- 寫在前面
- E 簽到
- F 簽到
- J BFS
- B 帶權並查集
- H 圖論
- I 數學
- L 樹形DP,容斥
- M 字串,離線,單調性
- G 貪心
- 寫在最後
寫在前面
比賽地址:https://codeforces.com/gym/103427。
以下按個人向難度排序。
唉唉國慶 vp 三場唯一打的還像人的一場,最後手裡還有兩道題在寫可惜都沒出來呃呃。
被樹上揹包沙勒呃呃呃要苦學樹上揹包!
E 簽到
我看都沒看。
code by wenqizhi:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 5;
char s[N];
int main()
{
scanf("%s", s + 1);
int cnt = 0, len = strlen(s + 1);
for(int i = 1; i <= len - 4; ++i)
{
if(s[i] == 'e' && s[i + 1] == 'd' && s[i + 2] == 'g' && s[i + 3] == 'n' && s[i + 4] == 'b') ++cnt;
}
printf("%d\n", cnt);
return 0;
}
F 簽到
直接大力列舉字尾暴力做然後大力排序即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1010;
//=============================================================
int n, cnt[21], map[21];
std::string s, ans[kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n;
std::cin >> s; s = "$" + s;
for (int i = 1; i <= n; ++ i) {
int num = 0;
for (int j = 0; j < 20; ++ j) cnt[j] = 0, map[j] = 0;
for (int j = i; j; -- j) {
if (cnt[s[j] - 'a'] == 0) map[s[j] - 'a'] = num, ++ num;
++ cnt[s[j] - 'a'];
}
for (int j = 1; j <= i; ++ j) ans[i].push_back(map[s[j] - 'a'] + 'a');
}
std::sort(ans + 1, ans + n + 1);
std::cout << ans[n] << "\n";
return 0;
}
J BFS
dztlb 大爹覺得一眼秒了但是唐了 1h 吃了三發都沒出我 tama 受不了了直接重新開了一遍這題發現純傻逼題 10min 過了呃呃
顯然僅需考慮起點和終點每位在環上的相對差值即可,即對於詢問 \((s, t)\) 等價於詢問 \((0, t - s)\)。
於是僅需預處理起點為 \(0\) 時,到所有狀態的最短路長度,邊權值均為 1,考慮 BFS 實現即可。
狀態數量僅有 \(10^4\) 個,每個狀態轉移時僅需大力列舉操作的區間和方向即可,轉移僅有 32 種,則總轉移次數不超過 \(10^6\) 次。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const int kInf = 1e9 + 2077;
//=============================================================
int dis[kN];
bool vis[kN];
//=============================================================
int trans(int u_, int l_, int r_, int v_) {
int v = 0;
for (int i = 0, pw10 = 1; i < 4; ++ i, pw10 *= 10) {
if (i < l_ || r_ < i) {
v += (u_ % 10) * pw10;
} else {
v += (u_ % 10 + v_ + 10) % 10 * pw10;
}
u_ /= 10;
}
return v;
}
void init() {
for (int i = 0; i < 10000; ++ i) dis[i] = kInf;
std::queue<int> q;
q.push(0);
dis[0] = 0;
vis[0] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int l = 0; l < 4; ++ l) {
for (int r = l; r < 4; ++ r) {
int v = trans(u, l, r, 1);
if (!vis[v]) vis[v] = 1, dis[v] = dis[u] + 1, q.push(v);
v = trans(u, l, r, -1);
if (!vis[v]) vis[v] = 1, dis[v] = dis[u] + 1, q.push(v);
}
}
}
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
init();
int T; std::cin >> T;
while (T --) {
std::string a, b; std::cin >> a >> b;
int t = 0;
for (int i = 0; i < 4; ++ i) t = 10 * t + (b[i] - a[i] + 10) % 10;
// std::cout << t << "\n";
std::cout << dis[t] << "\n";
}
return 0;
}
B 帶權並查集
wenqizhi 大爹秒了。
考慮將每個數看做一個點,發現給定的限制關係實際上構成了若干連通塊,不同連通塊內互不影響,僅需考慮每個連通塊內如何構造即可。
一開始的想法是考慮類似 2-SAT 的拆位,但是發現並不需要。發現對於某個連通塊內,只需要確定某一個數的取值,其他所有數的取值即可唯一確定,於是考慮帶權並查集維護聯通塊內每個數到根節點的異或和即可,手玩下容易得到合併聯通塊時對權值的影響。
然後再列舉每個聯通塊,拆位統計貢獻,根據帶權並查集的資訊,貪心確定該聯通塊的根節點這一位的取何值時,這一位貢獻和最小即可。
code by wenqizhi:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 1e5 + 5;
int f[N], xo[N], n, m;
int find(int x)
{
if(x == f[x]) return x;
int fa = f[x];
f[x] = find(f[x]);
xo[x] ^= xo[fa];
return f[x];
}
vector<int> num[N];
int cnt[100];
int main()
{
n = read(), m = read();
for(int i = 1; i <= n; ++i) f[i] = i;
for(int i = 1; i <= m; ++i)
{
int u = read(), v = read(), w = read();
int fu = find(u), fv = find(v);
if(fv != fu)
{
xo[fv] ^= xo[v] ^ xo[u] ^ w, f[fv] = fu;
}else
{
if((xo[u] ^ xo[v]) != w)
{
printf("-1\n");
return 0;
}
}
}
for(int i = 1; i <= n; ++i)
{
find(i);
if(i != f[i]) num[f[i]].emplace_back(xo[i]);
}
ll ans = 0;
for(int t = 1; t <= n; ++t)
if(num[t].size())
{
ll tot = num[t].size();
for(int i = 0; i <= 29; ++i) cnt[i] = 0;
for(int i = 0; i < tot; ++i)
{
int x = num[t][i];
for(int j = 0; j <= 29; ++j)
if((1 << j) & x) ++cnt[j];
}
for(int i = 29; i >= 0; --i) ans += min((1ll << i) * cnt[i], (1ll << i) * (tot - cnt[i] + 1));
}
printf("%lld\n", ans);
return 0;
}
H 圖論
dztlb 大爹看了眼秒了但是鑑於他太唐了不敢讓他上機於是我寫的。
這是什麼?這是線圖。容易發現線圖的匹配就是原圖的邊匹配,當原圖兩條邊有公共點時即可匹配,貢獻即兩條邊權之和。手玩下發現當原圖邊的數量為偶數時,總能全部匹配完並取得全部貢獻。
當原圖邊數量為奇數時,則最優方案一定是一定僅有一條邊無法匹配。若存在僅有某條邊無法匹配的方案,則刪掉這條邊後一定是得到一張邊數為偶數的連通圖,或是邊數為偶數的兩個聯通塊。則一條邊不合法的情況僅為:
- 是割邊;
- 該邊連線的兩個聯通塊內的邊數均為奇數。
考慮邊雙縮點,求得割邊並維護邊雙內邊的數量。顯然邊雙縮點後得到的是一棵樹,樹邊均為割邊,在這棵樹上 dfs 考慮斷邊後子樹內邊的數量是否為奇數即可。
稍微有點難寫呃呃。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 5e5 + 10;
const int kM = 2e6 + 10;
//=============================================================
int n, m;
int edgenum = 1, head[kN], v[kM], w[kM], ne[kM];
int dfnnum, dfn[kN], low[kN];
bool bridge[kM];
int dccnum, indcc[kN], sz[kN];
std::vector<pr <int, int> > edge[kN];
LL ans, edgesum;
bool tag[kM];
//=============================================================
void addedge(int u_, int v_, int w_) {
v[++ edgenum] = v_;
w[edgenum] = w_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
}
void tarjan(int u_, int from_) {
dfn[u_] = low[u_] = ++ dfnnum;
for (int i = head[u_]; i > 1; i = ne[i]) {
int v_ = v[i];
if (!dfn[v_]) {
tarjan(v_, i);
if (low[v_] > dfn[u_]) bridge[i] = bridge[i ^ 1] = 1;
low[u_] = std::min(low[u_], low[v_]);
} else if (i != (from_ ^ 1)) {
low[u_] = std::min(low[u_], dfn[v_]);
}
}
}
void dfs(int u_, int id_) {
indcc[u_] = id_;
for (int i = head[u_]; i > 1; i = ne[i]) {
int v_ = v[i];
if (indcc[v_] || bridge[i]) continue;
dfs(v_, id_);
}
}
void dfs1(int u_, int fa_) {
for (auto [v_, i]: edge[u_]) {
if (v_ == fa_) continue;
dfs1(v_, u_);
if (sz[v_] % 2 == 1 && (m - sz[v_] - 1) % 2 == 1) {
tag[i] = 1;
}
sz[u_] += sz[v_] + 1;
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= m; ++ i) {
int u_, v_, w_; std::cin >> u_ >> v_ >> w_;
edgesum += w_;
addedge(u_, v_, w_), addedge(v_, u_, w_);
}
if (m % 2 == 0) {
std::cout << edgesum << "\n";
return 0;
}
for (int i = 1; i <= n; ++ i) if (!dfn[i]) tarjan(i, 0);
for (int i = 1; i <= n; ++ i) if (!indcc[i]) dfs(i, ++ dccnum);
for (int u_ = 1; u_ <= n; ++ u_) {
for (int i = head[u_]; i > 1; i = ne[i]) {
if (i % 2 == 1) continue;
int v_ = v[i];
if (indcc[u_] == indcc[v_]) {
++ sz[indcc[u_]];
continue;
}
edge[indcc[u_]].push_back(mp(indcc[v_], i));
edge[indcc[v_]].push_back(mp(indcc[u_], i));
}
}
dfs1(1, 0);
for (int i = 2; i <= 2 * m; i += 2) {
if (!tag[i]) ans = std::max(ans, edgesum - w[i]);
}
std::cout << ans << "\n";
return 0;
}
I 數學
wenqizhi 大爹真是大爹啊我草,大力解方程過了。
code by wenqizhi:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
int x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const double eps = 1e-10;
struct node
{
double A, B;
node(){ A = 0, B = 0; }
node friend operator + (node a, node b)
{
node c;
c.A = a.A + b.A;
c.B = a.B + b.B;
return c;
}
node friend operator - (node a, node b)
{
node c;
c.A = a.A - b.A;
c.B = a.B - b.B;
return c;
}
node friend operator * (node a, node b)
{
node c;
c.A = a.A * b.A - a.B * b.B;
c.B = a.A * b.B + a.B * b.A;
return c;
}
node friend operator / (node a, node b)
{
node c, d;
d.A = b.A, d.B = -b.B;
double x = b.A * b.A + b.B * b.B;
d.A /= x, d.B /= x;
return a * d;
}
}z1, z2, z3, z0, w1, w2, w3, w0, X, Y, K, ans, I;
bool check(node a)
{
double x = sqrt(a.A * a.A + a.B * a.B );
if(fabs(x) < eps) return false;
return true;
}
void solve()
{
I.A = 1, I.B = 0;
scanf(" %lf %lf %lf %lf", &z1.A , &z1.B , &w1.A , &w1.B );
scanf(" %lf %lf %lf %lf", &z2.A , &z2.B , &w2.A , &w2.B );
scanf(" %lf %lf %lf %lf", &z3.A , &z3.B , &w3.A , &w3.B );
scanf(" %lf %lf", &z0.A , &z0.B );
if(check(((z2 - z1) * (w3 * z3 - w1 * z1) - (z3 - z1) * (w2 * z2 - w1 * z1))) && check(z3 - z1))
{
// printf("DDD\n");
X = ((z3 - z1) * (w2 - w1) - (z2 - z1) * (w3 - w1)) / ((z2 - z1) * (w3 * z3 - w1 * z1) - (z3 - z1) * (w2 * z2 - w1 * z1));
Y = ((w3 * z3 - w1 * z1) * X + w3 - w1) / (z3 - z1);
K = w1 * z1 * X + w1 - z1 * Y;
ans = (Y * z0 + K) / (X * z0 + I);
printf("%.10lf %.10lf\n", ans.A , ans.B );
return ;
}
if(check(((z3 - z1) * (w2 - w1) - (z2 - z1) * (w3 - w1))) && check(z3 - z1))
{
// printf("CCC\n");
X = ((z2 - z1) * (w3 * z3 - w1 * z1) - (z3 - z1) * (w2 * z2 - w1 * z1)) / ((z3 - z1) * (w2 - w1) - (z2 - z1) * (w3 - w1));
Y = (w3 * z3 - w1 * z1 + (w3 - w1) * X) / (z3 - z1);
K = w1 * z1 + w1 * X - z1 * Y;
ans = (Y * z0 + K) / (z0 + X);
printf("%.10lf %.10lf\n", ans.A , ans.B );
return ;
}
if(check(((w2 - w1) * (w3 * z3 - w1 * z1) - (w3 - w1) * (w2 * z2 - w1 * z1))) && check(w2 - w1))
{
// printf("AAA\n");
X = ((w2 - w1) * (z3 - z1) - (w3 - w1) * (z2 - z1)) / ((w2 - w1) * (w3 * z3 - w1 * z1) - (w3 - w1) * (w2 * z2 - w1 * z1));
Y = ((z2 - z1) - (w2 * z2 - w1 * z1) * X) / (w2 - w1);
K = w1 * z1 * X + w1 * Y - z1;
ans = (z0 + K) / (X * z0 + Y);
printf("%.10lf %.10lf\n", ans.A , ans.B );
return ;
}
if(check(((z1 * z3 * w1 - z1 * z3 * w3) * (z1 * w2 - z2 * w1) - (z1 * z2 * w1 - z1 * z2 * w2) * (z1 * w3 - z3 * w1))) && check((z1 * z2 * w3 - z1 * z3 * w1)) && check(z1))
{
// printf("BBB\n");
X = ((z1 * z2 * w1 - z1 * z2 * w2) * (z3 - z1) - (z1 * z3 * w1 - z1 * z3 * w3) * (z2 - z1)) / ((z1 * z3 * w1 - z1 * z3 * w3) * (z1 * w2 - z2 * w1) - (z1 * z2 * w1 - z1 * z2 * w2) * (z1 * w3 - z3 * w1));
Y = (z3 * w1 * X - z1 * w3 * X + z1 - z3) / (z1 * z2 * w3 - z1 * z3 * w1);
K = (w1 * z1 * Y + w1 * X - I) / (z1);
ans = (K * z0 + I) / (Y * z0 + X);
printf("%.10lf %.10lf\n", ans.A , ans.B );
return ;
}
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
L 樹形DP,容斥
賽時想到樹形DP但是狀態假了呃呃。
M 字串,離線,單調性
草這題做過詢問區間任意的版本而且打算出到新生賽上,於是直接拿過來秒了。
題解也搬一下:
一個顯然的性質是子串 \(s[l:r]\) 中字典序最大的子串的右端點一定是 \(r\)。
考慮將所有詢問 \((l, r)\) 離線,按右端點 \(r\) 升序排序後順序列舉右端點 \(r\),則此時所有詢問的答案一定以 \(r\) 結尾。在此過程中維護以 \(r\) 結尾的所有子串 \(s[1:r] \sim s[r:r]\) 的字典序大小關係並回答詢問。
發現對於某個確定的右端點 \(r\),當詢問左端點 \(l\) 增加時,作為答案的子串 \(s[p:r]\) 的左端點一定是單調不降的,字典序一定是單調不增的。則考慮在列舉 \(r\) 的同時,對有貢獻的左端點,維護一個按照答案的左端點 \(p\) 遞增,字典序遞減的序列,回答詢問時二分即得到答案。
對於兩個子串 \(s[p_1: r], s[p_2: r](p_1 < p_2)\),若有 \(s[p_1: r]<s[p_2: r]\) 則當 \(r\) 增加時恆有 \(s[p_1: r']<s[p_2: r']\) 成立,又 \([p_2, r]\in [p_1, r]\) 則 \(s[p_1: r']\) 對之後的任何詢問均無貢獻,可以將其刪去。
更一般地,記 \(\operatorname{lcp}(s_1, s_2)\) 表示兩個字串 \(s_1, s_2\) 的最長公共字首的長度,對於兩個原字串的字尾 \(s[p_1, n], s[p_2, n](p_1 < p_2)\),記 \(\operatorname{lcp} = \operatorname{lcp}(s[p_1:n], s[p_2:n])\):
- 若 \(s_{p_1 + \operatorname{lcp}}>s_{p_2 + \operatorname{lcp}}\),則恆有 \(s[p_1:r] > s[p_2:r]\)。
- 若 \(s_{p_1 + \operatorname{lcp}}<s_{p_2 + \operatorname{lcp}}\),則當 \(r<p_2 + \operatorname{lcp}\) 時有 \(s[p_1:r] > s[p_2:r]\);當 \(r\ge p_2 + \operatorname{lcp}\) 時有 \(s[p_1:r] < s[p_2:r]\)。則在列舉到 \(r=p_2 + \operatorname{lcp}\) 後 \(s[p_1:r]\) 對之後的任何詢問都無貢獻,可以將其刪去。
- 對於上述情況,我們稱左端點 \(p_2\) 支配了左端點 \(p_1\)。發現支配關係實際上構成了一個 DAG 的結構,當左端點 \(p_1\) 被刪除時,顯然被 \(p_1\) 支配的所有左端點之後也不會有貢獻,應當遞迴地把它們全部刪除。
考慮在上述過程中,額外維護此時沒有被支配的左端點序列,用來輔助有貢獻的左端點的維護。顯然該序列也是有上述按照答案的左端點 \(p\) 遞增,字典序遞減的單調性的。為了方便刪除均使用 set 實現即可。
在列舉右端點時維護 \(\operatorname{D}_r\) 表示列舉到 \(r\) 時應刪除哪些左端點,\(\operatorname{G}_p\) 表示左端點 \(p\) 直接支配哪些左端點。每次右端點 \(r\) 增加時,不斷比較序列尾部元素 \(p_1\) 與當前入棧元素 \(r\) 的 \(s_{p_1 + \operatorname{lcp}}, s_{r+ \operatorname{lcp}}\),若有 \(s_{p_1 + l}< s_{r + l}\) 說明 \(p_1\) 被 \(i\) 支配而應當將 \(p_1\) 彈出並更新 \(\operatorname{D}_{r+\operatorname{lcp}}\) 與 \(\operatorname{G}_{r}\);若有 \(s_{p_1 + l}> s_{r + l}\) 由單調性可知此時序列內任何左端點均不會被 \(r\) 支配,可以結束列舉。
然後根據此時的 \(\operatorname{D}_r\) 與 \(\operatorname{G}\) 遞迴刪除無貢獻的左端點,再列舉詢問二分即可。
那麼如何求兩個串的 \(\operatorname{lcp}\)?同樣考慮二分兩個子串長度相同的字首的長度並雜湊檢查,即可在 \(O(\log n)\) 時間複雜度內解決。當然如果你是字串大神直接掏出字尾陣列+ST表就秒了哈哈。
考慮到大家應該都不會字尾陣列所以本題 std 使用了雜湊二分實現。總複雜度 \(O((|s|+m)\log^2 |s|)\) 級別。
參考:https://blog.csdn.net/C20181220_xiang_m_y/article/details/95516076
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
const LL c1 = 29;
const LL c2 = 1145141;
const LL p1 = 1e9 + 7;
const LL p2 = 998244353;
//=============================================================
int n;
LL pw1[kN], pw2[kN], h1[kN], h2[kN];
std::string s;
std::vector<int> st, del[kN], g[kN];
std::set<int> suf;
//=============================================================
LL hash1(int l_, int r_) {
return (h1[r_] - h1[l_ - 1] * pw1[r_ - l_ + 1] % p1 + p1) % p1;
}
LL hash2(int l_, int r_) {
return (h2[r_] - h2[l_ - 1] * pw2[r_ - l_ + 1] % p2 + p2) % p2;
}
bool equal(int l1_, int r1_, int l2_, int r2_) {
return hash1(l1_, r1_) == hash1(l2_, r2_) &&
hash2(l1_, r1_) == hash2(l2_, r2_);
}
int lcp(int i_, int j_) {
int ret = 0;
for (int l = 1, r = std::min(n - i_, n - j_) + 1; l <= r; ) {
int mid = (l + r) >> 1;
if (equal(i_, i_ + mid - 1, j_, j_ + mid - 1)) {
ret = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ret;
}
void dfs(int u_) {
if (suf.find(u_) == suf.end()) return ;
suf.erase(u_);
for (auto v_: g[u_]) dfs(v_);
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> s; n = s.length(); s = "$" + s;
pw1[0] = pw2[0] = 1;
for (int i = 1; i <= n; ++ i) {
pw1[i] = c1 * pw1[i - 1] % p1;
pw2[i] = c2 * pw2[i - 1] % p2;
h1[i] = (c1 * h1[i - 1] + s[i]) % p1;
h2[i] = (c2 * h2[i - 1] + s[i]) % p2;
}
for (int i = 1; i <= n; ++ i) {
while (!st.empty()) {
int j = st.back(), len = lcp(i, j);
if (s[j + len] > s[i + len]) break;
del[i + len].push_back(j);
g[i].push_back(j);
st.pop_back();
}
st.push_back(i), suf.insert(i);
for (auto j: del[i]) dfs(j);
int p = *suf.lower_bound(1);
std::cout << p << " " << i << '\n';
}
return 0;
}
G 貪心
寫在最後
學到了什麼:
- L: