2022-2023 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2022) === 比賽鏈結 --- https://codeforces.com/gym/104030 比賽影片 --- {%youtube G12C-BFRWO8%} 記分板 --- ![image](https://hackmd.io/_uploads/Bymr50bRA.png) 題解 --- ### A. Ace Arbiter --- #### 題目摘要 Alice和Bob打桌球,11分結束,第1局由Alice發球,2、3局由Bob發球,之後每2局交換攻守,計分的方式是先寫本局發球人的分數再寫另一人的分數。然而紀錄的人會亂記分,可能有一場會記多次或不記,但順序不會亂改。問這場比賽可不可能存在,若不存在,請輸出最早記錯的一局 #### 想法 QQ英文看不懂吃超多penalty,這題只是簡單判case題 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; int turn(int x){ if (x==0) return 0; x--; x/=2; if (x&1) return 0; return 1; } int main(){ int n; scanf("%d", &n); int lx=-1, ly=-1; for(int i=1;i<=n;i++){ int x, y; scanf("%d-%d", &x, &y); if (x==11&&y==11){ printf("error %d\n", i); return 0; } if (lx==-1||ly==-1){ lx=x, ly=y; }else{ if (lx+ly==x+y){ if (lx==x&&ly==y) continue; printf("error %d\n", i); return 0; } if (lx+ly<x+y){ if (lx==11||ly==11){ printf("error %d\n", i); return 0; } if (turn(lx+ly)!=turn(x+y)) swap(lx, ly); if (lx>x||ly>y){ printf("error %d\n", i); return 0; }else{ lx=x, ly=y; continue; } }else{ printf("error %d\n", i); return 0; } } } printf("ok\n"); } ``` ::: ### B. Berry Battle --- #### 題目摘要 給定一個$N$個點的樹,每個點上都有一個莓果跟一隻螞蟻,每當你拿掉點$v$的莓果,那麼樹上所有螞蟻都會往$v$前進一步,要求輸出一個順序,在拿完所有莓果的前提下,不能有任何一個瞬間所有螞蟻重疊在同一個點。 #### 想法 注意到因為所有螞蟻會一起前進,會使螞蟻merge的情況只有點到有螞蟻的點以及兩隻螞蟻在不同子樹上往同向前進,容易觀察到如果選完樹直徑上的點,那麼其他所有子樹的螞蟻都會走到樹直徑上,接下來只要輪流把除了樹直徑上的子樹從直徑上往外點就可以了,因為螞蟻都在直徑上,不會滿足merge的第二個條件,而除了直徑的子樹也都不會有螞蟻。 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define rep(a, b, c) for (int a = b; a < c; a++) #define ll long long #define all(x) (x).begin(), (x).end() #define pb push_back const int IINF = 1e9 + 7; const ll LINF = 1e18 + 5; int main(){ int n; cin >> n; vector<vector<int>> adj(n); rep (i, 0, n - 1) { int a, b; cin >> a >> b; a--, b--; adj[a].pb(b); adj[b].pb(a); } vector<int> dep(n); auto dfs = [&](auto self, int u, int pa) -> void { for (int v : adj[u]) { if (v == pa) continue; dep[v] = dep[u] + 1; self(self, v, u); } }; dep[0] = 0; dfs(dfs, 0, -1); int st = max_element(all(dep)) - dep.begin(); dep[st] = 0; dfs(dfs, st, -1); int ed = max_element(all(dep)) - dep.begin(), red = ed, sred; if (dep[ed] < 3) { cout << "NO\n"; exit(0); } vector<bool> vis(n, 0); cout << "YES\n"; vector<int> d; for (int x : adj[ed]) if (dep[x] + 1 == dep[ed]) { ed = x; break; } sred = ed; for (int x : adj[ed]) if (dep[x] + 1 == dep[ed]) { ed = x; break; } vis[ed] = vis[red] = vis[sred] = 1; d.pb(ed); d.pb(red); d.pb(sred); // cout << ed << '\n'; while (ed != st) { cout << ed + 1 << ' '; for (int x : adj[ed]) if (dep[x] + 1 == dep[ed]) { ed = x; break; } vis[ed] = 1; d.pb(ed); } cout << ed + 1 << ' ' << sred + 1 << ' ' << red + 1 << ' '; auto dfs2 = [&](auto self, int u, int pa) -> void { for (int v : adj[u]) { if (v == pa || vis[v]) continue; vis[v] = 1; cout << v + 1 << ' '; self(self, v, u); } }; for (int x : d) { dfs2(dfs2, x, -1); } } ``` ::: ### C. Coffee Cup Combo --- #### 題目摘要 給你一個01字串,1代表可以喝一杯咖啡並且帶兩杯在身上,0會消耗一杯咖啡,問可以有幾天有咖啡喝 #### 想法 簽到,直接記剩幾杯就好 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; int n; string s; int main(){ cin >> n >> s; int ans = 0, left = 0; for(int i = 0; i < n ; i++){ if(s[i]=='1'){ ans++; left = 2; }else if(left>0){ ans++; left--; } } cout << ans; } ``` ::: ### D. Disc District --- #### 題目摘要 給一個圓心在$(0,\, 0)$半徑為$r$的圓,請輸出一個在圓外的格子點,使得沒其他在圓外的格子點比他離圓更近。 #### 想法 輸出$(1, r)$即可,我賽中沒想到所以使用了二分搜。 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define rep(a, b, c) for (int a = b; a < c; a++) #define ll long long const int IINF = 1e9 + 7; const ll LINF = 1e18 + 5; int main(){ ll n; cin >> n; ll x, y, ans = LINF; rep (i, 0, n + 2) { ll l = -1, r = IINF; while (r - l > 1) { ll mid = l + r >> 1; if (1LL * i * i + mid * mid > n * n) r = mid; else l = mid; } if (1LL * i * i + r * r < ans) { ans = 1LL * i * i + r * r; x = i, y = r; } } cout << x << ' ' << y << '\n'; } ``` ::: ### E. Enigmatic Enumeration --- #### 題目摘要 問一張不一定連通的簡單圖的最小環數量 #### 想法 注意到$n$很小,可以枚舉每個點當作環上其中一個點,然後跑bfs找最短路,再判會不會有環就好,而環分成奇數邊與偶數邊: 奇數環會滿足有條邊兩端的距離會一樣,所以只要跑完最短路後再枚舉所有邊找最小環就好 偶數環會滿足有個點有至少兩種路徑,所以只要跑完最短路後再枚舉所以點找最小環就好 這樣可能會有跑的最短路會共用邊的情況,然而這是能被忽略的,因為若共邊,則存在一個更小環,而更小環一定會被算到 算出最短環長度後,接下來就是算有多少個,枚舉最短路的方法數再做相乘就能解決,然而每個環會被重複算到環長度次,記得要除掉以免多算 賽中邊的大小開錯得WA,一直以為是實作爛而de不到bug,賽後才被隊友發現QAQ :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int, int> pii; #define F first #define S second const int N=3005; vector<int> v[N]; int dis[N]; ll dp[N]; pii p[N<<1]; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n, m; cin >> n >> m; for(int i=0;i<m;i++){ int a, b; cin >> a >> b; v[a].push_back(b); v[b].push_back(a); p[i]={a, b}; } queue<int> q; int len=n+1; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++) dis[j]=n+1; for(int j=1;j<=n;j++) dp[j]=0; dis[i]=0; dp[i]=1; q.push(i); while(!q.empty()){ int x=q.front(); q.pop(); for(auto e:v[x]){ if (dis[e]>dis[x]+1){ dis[e]=dis[x]+1; dp[e]=dp[x]; q.push(e); }else if (dis[e]==dis[x]+1){ dp[e]+=dp[x]; } } } for(int j=1;j<=n;j++) if (dp[j]>1) len=min(len, dis[j]<<1); for(int j=0;j<m;j++) if (dis[p[j].F]==dis[p[j].S]) len=min(len, dis[p[j].F]+dis[p[j].S]+1); } // cout << len << '\n'; ll ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++) dis[j]=n+1; for(int j=1;j<=n;j++) dp[j]=0; dis[i]=0; dp[i]=1; q.push(i); while(!q.empty()){ int x=q.front(); q.pop(); for(auto e:v[x]){ if (dis[e]>dis[x]+1){ dis[e]=dis[x]+1; dp[e]=dp[x]; q.push(e); }else if (dis[e]==dis[x]+1){ dp[e]+=dp[x]; } } } if (len&1){ for(int j=0;j<m;j++) if (dis[p[j].F]==(len>>1)&&dis[p[j].S]==(len>>1)){ ans+=dp[p[j].F]*dp[p[j].S]; } }else{ for(int j=1;j<=n;j++) if (dis[j]==(len>>1)){ ans+=dp[j]*(dp[j]-1)>>1; } } } ans/=len; cout << ans << '\n'; } ``` ::: ### F. Foreign Football --- #### 題目摘要 給你$n$個字串兩兩組合的表,問題是否有唯一解,多組解或無解,若是唯一解請輸出那$n$個字串 #### 想法 假設每個字串的長度為$l_i$,那總長度$s=\sum\limits_{i=1}^{n}l_i$。第$i$列的長度是$(n-1)\times l_i+\sum\limits_{j=1}^{n,\ j\not =i} l_j$,也就是$(n-2)l_i+s$,因此能求到$l_i$,也就是能知道第$i$個字串長怎樣,這樣能做完$n>2$的情況。在$n=2$的情況,只要把其中一個字串複製到屁股,再看有沒有子字串等於另一個字串就好 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define rep(a, b, c) for (int a = b; a < c; a++) #define ll long long #define all(x) (x).begin(), (x).end() #define pb push_back #define pii pair<int, int> const int IINF = 1e9 + 7, MAXN = 2e6 + 5; const ll LINF = 1e18 + 5; const int p = 131, MOD = 1e9 + 7; ll fpow(ll x, int exp) { ll res = 1; while (exp) { if (exp & 1) res = res * x % MOD; x = x * x % MOD; exp >>= 1; } return res; } int main(){ int n; cin >> n; vector s(n, vector<string>(n)); int len = 0; rep (i, 0, n) rep (j, 0, n) { cin >> s[i][j]; if (i != j) len += s[i][j].size(); } if (n == 2) { string a = s[0][1], b = s[1][0]; if (a.size() != b.size()) { cout << "NONE\n"; exit(0); } b += b; int l = -1, cnt = 0, sz = b.size(); string ans = ""; vector<ll> hash(sz + 1); hash[0] = 0; rep (i, 0, sz) hash[i + 1] = (hash[i] * p % MOD + b[i]) % MOD; ll ha = 0; for (char c : a) ha = (ha * p % MOD + c) % MOD; rep (i, 1, a.size()) { if (ha == (hash[i + a.size()] - hash[i] * fpow(p, a.size()) % MOD + MOD) % MOD) { l = i; cnt++; } } if (l == -1) cout << "NONE\n"; else if (cnt > 1) cout << "MANY\n"; else cout << "UNIQUE\n" << b.substr(l + a.size(), a.size() - l) << '\n' << b.substr(0, l) << '\n'; } else { if (len % (2 * n - 2)) { cout << "NONE\n"; exit(0); } len /= 2 * n - 2; vector<string> ans; rep (i, 0, n) { int slen = 0; rep (j, 0, n) if (i != j) slen += s[i][j].size(); slen -= len; if (slen % (n - 2) || slen == 0) { cout << "NONE\n"; exit(0); } slen /= n - 2; ans.pb(s[i][(i + 1) % n].substr(0, slen)); } rep (i, 0, n) rep (j, 0, n) if (i != j) { if (ans[i] + ans[j] != s[i][j]) { cout << "NONE\n"; exit(0); } } cout << "UNIQUE\n"; for (string t : ans) cout << t << '\n'; } } ``` ::: ### G. Graduation Guarantee --- #### 題目摘要 給你每題答對的機率,答對得1分,答錯-1分,不回答0分,問得$k$分最大機率 #### 想法 設$dp_{i, j}$為回答$i$題得$j$分的最大機率,因此轉移能變成 \begin{cases} dp_{0,\, 0}=1 \\ dp_{i,\, j}=dp_{i-1,\, j-1}\times p_i+dp_{i-1, \, j+1}\times (1-p_i) \end{cases} 注意到若要回答$i$題,一定會選答對機率越大的題,所以要先排序再套dp 要狀壓,不然會吃MLE :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; typedef long long ll; typedef long double ld; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n, k; cin >> n >> k; vector<ld> a(n); for(int i=0;i<n;i++) cin >> a[i]; sort(a.begin(), a.end(), greater<ld>()); vector<ld> dp(2*(n+1), 0.0); dp[n]=1.0; ld ans=0.0; for(int i=0;i<n;i++){ vector<ld> tmp(2*(n+1), 0.0); for(int j=-n;j<=n;j++){ if (j<n) tmp[j+n+1]+=dp[j+n]*a[i]; if (j>-n) tmp[j+n-1]+=dp[j+n]*(1.0-a[i]); } dp.swap(tmp); ld res=0.0; for(int j=k;j<=n;j++) res+=dp[j+n]; ans=max(ans, res); } cout << fixed << setprecision(10) << ans << '\n'; } ``` ::: ### H. Highest Hill --- #### 題目摘要 有好多山的高度,求山峰與兩側最近山谷的最大差 #### 想法 分兩次做,一次求左邊的山谷,一次求右邊的,最後再更新答案 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define rep(a, b, c) for (int a = b; a < c; a++) #define ll long long #define pb push_back const int IINF = 1e9 + 7; const ll LINF = 1e18 + 5; int main(){ int n; cin >> n; vector<ll> a(n); vector<ll> low1(n), low2(n); rep (i, 0, n) cin >> a[i]; for(int i=0;i<n;i++) low1[i]=low2[i]=a[i]; ll mn=a[0]; for(int i=1;i<n;i++){ if (a[i-1]<=a[i]){ mn=min(mn, a[i]); }else{ mn=a[i]; } low1[i]=mn; } mn=a[n-1]; for(int i=n-2;~i;--i){ if (a[i+1]<=a[i]){ mn=min(mn, a[i]); }else{ mn=a[i]; } low2[i]=mn; } ll ans=0; for(int i=0;i<n;i++) ans=max(ans, a[i]-max(low1[i], low2[i])); cout << ans << '\n'; } ``` ::: ### I. Icy Itinerary --- #### 題目摘要 給你一張圖,你有兩種走路方式:走有邊或無邊,兩種方式最多只能切換一次,問從點1開始拜訪點的順序 #### 想法 假設目前已經走完一個合法路徑,現在要在插入一個點,問題是這點要插在哪 若走完的合法路徑只有用一種方法走,那不論目前點和最後的點是有邊無邊,可以直接接在最後 若合法路徑用兩種方式,先找到一個前後的走路方式不同的點(mid),再判斷目前的點和mid該怎麼走,若與先走的走路方式一樣,可以將它插在mid後面,否則把它插在mid前面,而這些都保證做完後還是合法路徑 而要維護插入點,可以用link list實作,而要查詢兩點是否有邊,可以用set維護,複雜度$O(n\log n)$ :::spoiler 實作 ```cpp= #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx,popcnt,sse4,abm") #include<bits/stdc++.h> using namespace std; #define ORENOOSHIKYOUMOKAWAIII ios::sync_with_stdio(0), cin.tie(0) const int N=3e5+10; int pre[N], suf[N], col[N]; set<int> v[N]; void add(int a, int b){ pre[b]=a; suf[a]=b; } void rmv(int a, int b){ suf[a]=a; pre[b]=b; } int main(){ ORENOOSHIKYOUMOKAWAIII; int n, m; cin >> n >> m; for(int i=0;i<m;i++){ int a, b; cin >> a >> b; v[a].insert(b); v[b].insert(a); } for(int i=1;i<=n;i++) pre[i]=suf[i]=i; int bac1=0, bac2=0, hd=0; if (v[2].find(1)!=v[2].end()) col[1]=col[2]=1, bac1=2; else col[1]=col[2]=2, bac2=2; add(1, 2); for(int i=3;i<=n;i++){ if (hd==0){ if (col[1]==1){ if (v[i].find(bac1)!=v[i].end()){ add(bac1, i); bac1=i; col[i]=1; }else{ hd=i; col[i]=2; bac2=i; } }else{ if (v[i].find(bac2)==v[i].end()){ add(bac2, i); bac2=i; col[i]=2; }else{ hd=i; col[i]=1; bac1=i; } } }else{ if (col[1]==1){ if (v[i].find(bac1)!=v[i].end()){ if (v[i].find(hd)!=v[i].end()){ int tmp=suf[hd]; if (hd!=tmp) rmv(hd, tmp); add(bac1, i); col[i]=1; add(i, hd); col[hd]=1; bac1=hd; if (hd==tmp) hd=0; else hd=tmp; }else{ add(bac1, i); col[i]=1; bac1=i; } }else{ if (v[i].find(pre[bac1])!=v[i].end()){ int tmp=pre[bac1]; rmv(tmp, bac1); add(tmp, i); col[i]=1; add(bac1, hd); col[bac1]=2; hd=bac1; bac1=i; }else{ int tmp=pre[bac1]; rmv(tmp, bac1); add(i, bac1); col[i]=2; add(bac1, hd); col[bac1]=2; hd=i; bac1=tmp; if (bac1==1){ add(1, i); col[1]=2; bac1=0; hd=0; } } } }else{ if (v[i].find(bac2)==v[i].end()){ if (v[i].find(hd)==v[i].end()){ int tmp=suf[hd]; if (hd!=tmp) rmv(hd, tmp); add(bac2, i); col[i]=2; add(i, hd); col[hd]=2; bac2=hd; if (hd==tmp) hd=0; else hd=tmp; }else{ add(bac2, i); col[i]=2; bac2=i; } }else{ if (v[i].find(pre[bac2])==v[i].end()){ int tmp=pre[bac2]; rmv(tmp, bac2); add(tmp, i); col[i]=2; add(bac2, hd); col[bac2]=1; hd=bac2; bac2=i; }else{ int tmp=pre[bac2]; rmv(tmp, bac2); add(i, bac2); col[i]=1; add(bac2, hd); col[bac2]=1; hd=i; bac2=tmp; if (bac2==1){ add(1, i); col[1]=1; bac2=0; hd=0; } } } } } } int x=1; while(x!=suf[x]){ cout << x << ' '; x=suf[x]; } cout << x << ' '; if (hd!=0){ x=hd; while(x!=suf[x]){ cout << x << ' '; x=suf[x]; } cout << x << '\n'; } } ``` ::: ### J. Junk Journey --- #### 題目摘要 給二維點上一些車車以及你的起點及車車的終點,當你推車車整排的車車會一起被推,也就是沒有兩台車車會在同一個點,車車進終點會消失,請你實作你把所有車車推進終點的走路方向,方法數要小於$10 ^ 5$。 #### 想法 純實作題,方法數足夠大不要亂搞都不會超過。衝出去外面,把不小心帶出去的全部塞回去,接下來用一個像電風扇的方式把它們塞進終點,以終點為中心分成四塊,實作請參考下方code。 :::spoiler 實作 ```cpp= #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx,popcnt,sse4,abm") #include<bits/stdc++.h> using namespace std; #define ZTMYACANESOCUTE ios_base::sync_with_stdio(0), cin.tie(0) #define ll long long #define ull unsigned long long #define pb push_back #define all(a) (a).begin(), (a).end() #define debug(x) cerr << #x << " = " << x << '\n'; #define rep(X, a, b) for(int X = a; X < b; ++X) #define pii pair<int, int> #define pll pair<ll, ll> #define pld pair<ld, ld> #define ld long double #define F first #define S second pii operator + (const pii &p1, const pii &p2) { return make_pair(p1.F + p2.F, p1.S + p2.S); } pii operator - (const pii &p1, const pii &p2) { return make_pair(p1.F - p2.F, p1.S - p2.S); } pll operator + (const pll &p1, const pll &p2) { return make_pair(p1.F + p2.F, p1.S + p2.S); } pll operator - (const pll &p1, const pll &p2) { return make_pair(p1.F - p2.F, p1.S - p2.S); } template<class T> bool chmin(T &a, T b) { return (b < a && (a = b, true)); } template<class T> bool chmax(T &a, T b) { return (a < b && (a = b, true)); } #define lpos pos << 1 #define rpos pos << 1 | 1 template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << "," << p.second << ')'; } template<typename A> ostream& operator << (ostream &os, const vector<A> &p) { for(const auto &a : p) os << a << " "; os << '\n'; return os; } const int MAXN = 2e5 + 5, MOD = 998244353, IINF = 1e9 + 7, MOD2 = 1000000007; const double eps = 1e-9; const ll LINF = 1e18L + 5; const int B = 320; // mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); // int get_rand(int l, int r){ return uniform_int_distribution<int>(l, r)(rng); } ll fpow(ll x, ll exp, ll mod = LLONG_MAX){ ll res = 1; while(exp){ if(exp & 1) res = res * x % mod; x = x * x % mod; exp >>= 1;} return res; } const int L = 300, D = 330, R = 330, U = 300; void solve() { int n; cin >> n; pii s, t; cin >> s.F >> s.S >> t.F >> t.S; s = s + make_pair(300, 300), t = t + make_pair(300, 300); vector g(1005, vector<int>(1005, 0)); int done = 0; auto push = [&](pii &pos, int d) -> void { if (d == 1) { cout << "right\n"; pos.F++; int tmp = pos.F; while (g[tmp][pos.S]) tmp++; for (int i = tmp; i > pos.F; --i) swap(g[i][pos.S], g[i - 1][pos.S]); } else if (d == 0) { cout << "left\n"; pos.F--; int tmp = pos.F; while (g[tmp][pos.S]) tmp--; for (int i = tmp; i < pos.F; ++i) swap(g[i][pos.S], g[i + 1][pos.S]); } else if (d == 2) { cout << "down\n"; pos.S--; int tmp = pos.S; while (g[pos.F][tmp]) tmp--; for (int i = tmp; i < pos.S; ++i) swap(g[pos.F][i], g[pos.F][i + 1]); } else { cout << "up\n"; pos.S++; int tmp = pos.S; while (g[pos.F][tmp]) tmp++; for (int i = tmp; i > pos.S; --i) swap(g[pos.F][i], g[pos.F][i - 1]); } if (g[t.F][t.S]) { done++; g[t.F][t.S] = 0; } }; rep (i, 0, n) { int x, y; cin >> x >> y; x += 300, y += 300; g[x][y] = 1; } // 0 1 2 3 / u, d, l, r while (s.S < R + 1) push(s, 3); int last = R + 2; while (g[s.F][last]) last++; push(s, 0); while (s.S < last) push(s, 3); push(s, 1); while (s.S > R + 1) push(s, 2); // guarenteed that all the scooter's are inside the chunk // robot's are now at the right of the chunk while (done != n) { // RD while (t.F > s.F) push(s, 1); while (t.F < s.F) push(s, 0); rep (i, t.F, D + 1) { int cnt = 0; rep (j, t.S + 1, R + 1) cnt += g[i][j]; if (cnt == 0) { push(s, 1); continue; } if (i == t.F) { while (s.S > t.S + 1) push(s, 2); while (s.S <= R) push(s, 3); } else { while (s.S > t.S + cnt) push(s, 2); while (s.S <= R) push(s, 3); } push(s, 1); } // LD while (t.S < s.S) push(s, 2); while (t.S > s.S) push(s, 3); for (int i = t.S; i >= L; --i) { int cnt = 0; rep (j, t.F + 1, D + 1) cnt += g[j][i]; if (cnt == 0) { push(s, 2); continue; } if (i == t.S) { while (s.F > t.F + 1) push(s, 0); while (s.F <= D) push(s, 1); } else { while (s.F > t.F + cnt) push(s, 0); while (s.F <= D) push(s, 1); } push(s, 2); } // LU while (t.F > s.F) push(s, 1); while (t.F < s.F) push(s, 0); for (int i = t.F; i >= U; --i) { int cnt = 0; rep (j, L, t.S) cnt += g[i][j]; if (cnt == 0) { push(s, 0); continue; } if (i == t.F) { while (s.S < t.S - 1) push(s, 3); while (s.S >= L) push(s, 2); } else { while (s.S < t.S - cnt) push(s, 3); while (s.S >= L) push(s, 2); } push(s, 0); } // RU while (t.S < s.S) push(s, 2); while (t.S > s.S) push(s, 3); rep (i, t.S, R + 1) { int cnt = 0; rep (j, U, t.F) cnt += g[j][i]; if (cnt == 0) { push(s, 3); continue; } if (i == t.S) { while (s.F < t.F - 1) push(s, 1); while (s.F >= U) push(s, 0); } else { while (s.F < t.F - cnt) push(s, 1); while (s.F >= U) push(s, 0); } push(s, 3); } } } int main() { ZTMYACANESOCUTE; int T = 1; // cin >> T; while (T--) { solve(); } } ``` ::: ### K. Keyboard Queries --- #### 題目摘要 給一個不知道內容物的字串長度$\ n$,兩種操作 1. $1 \ \ell \ r$ 確定區間$[\ell, \ r]$為迴文 2. $2 \ a \ b \ x \ y$ 問$[a, b]$是否等於$[x, y]$,或是未知 #### 想法 首先先知道不等於的狀況只有$\ b - a \ne y - x$。 可以考慮用支援單點修改區間詢問的資料結構來維護字串的Hash值。 假定一開始為一個1~n的字串 給定一個回文等同於將左右對稱的位置合併成同個標號 可以使用並查集跟啟發式合併來維護字串每個位置目前的標號 但直接枚舉合併位置會太慢 可以使用二分搜從中間去找到第一個不是回文的範圍再往後枚舉(對稱會有單調性) 注意到合併最多只會做到$n - 1$次,因此複雜度$O(n \log^{2}n + Q \log n)$ 讚 實作好好顧到奇偶,並且注意並查集祖先同個標籤不要做合併,不然就會瘋狂TLE == :::spoiler 實作 ```cpp= #pragma GCC optimize("O3,unroll-loops") // #pragma GCC target("avx,popcnt,sse4,abm") #include<bits/stdc++.h> using namespace std; #define ZTMYACANESOCUTE ios_base::sync_with_stdio(0), cin.tie(0) #define ll long long #define ull unsigned long long #define pb push_back #define all(a) (a).begin(), (a).end() #define debug(x) cerr << #x << " = " << x << '\n'; #define rep(X, a, b) for(int X = a; X < b; ++X) #define pii pair<int, int> #define pll pair<ll, ll> #define pld pair<ld, ld> #define ld long double #define F first #define S second pii operator + (const pii &p1, const pii &p2) { return make_pair(p1.F + p2.F, p1.S + p2.S); } pii operator - (const pii &p1, const pii &p2) { return make_pair(p1.F - p2.F, p1.S - p2.S); } pll operator + (const pll &p1, const pll &p2) { return make_pair(p1.F + p2.F, p1.S + p2.S); } pll operator - (const pll &p1, const pll &p2) { return make_pair(p1.F - p2.F, p1.S - p2.S); } template<class T> bool chmin(T &a, T b) { return (b < a && (a = b, true)); } template<class T> bool chmax(T &a, T b) { return (a < b && (a = b, true)); } #define lpos pos << 1 #define rpos pos << 1 | 1 template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << "," << p.second << ')'; } template<typename A> ostream& operator << (ostream &os, const vector<A> &p) { for(const auto &a : p) os << a << " "; os << '\n'; return os; } const int MAXN = 1e5 + 5, MOD2 = 998244353, IINF = 1e9 + 7, MOD = 1000000007; const double eps = 1e-9; const ll LINF = 1e18L + 5; const int B = 320; // mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); // int get_rand(int l, int r){ return uniform_int_distribution<int>(l, r)(rng); } ll fpow(ll x, ll exp, ll mod = LLONG_MAX){ ll res = 1; while(exp){ if(exp & 1) res = res * x % mod; x = x * x % mod; exp >>= 1;} return res; } struct FenwickTree{ vector<ll> BIT; FenwickTree(int n) : BIT(n + 1, 0) {} void mod(int x, ll val) { while(x < BIT.size()){ BIT[x] += val; x += x & -x; } } ll query(int x) { ll res = 0; while (x) { res += BIT[x]; x -= x & -x; } return res; } ll rquery(int l, int r) { return query(r) - query(l - 1); } }; const int c = 131; int pa[MAXN]; int find(int x) { return pa[x] == x ? pa[x] : pa[x] = find(pa[x]); } void solve() { int n, q; cin >> n >> q; rep (i, 1, n + 1) pa[i] = i; FenwickTree h(n), h2(n); vector<vector<int>> id(n + 1); vector<ll> p(n + 1), inv(n + 1); p[0] = 1; rep (i, 1, n + 1) p[i] = p[i - 1] * c % MOD; inv[n] = fpow(p[n], MOD - 2, MOD); for (int i = n - 1; i >= 0; --i) inv[i] = inv[i + 1] * c % MOD; rep (i, 1, n + 1) { id[i].pb(i); h.mod(i, i * p[i] % MOD); h2.mod(n - i + 1, i * p[n - i + 1] % MOD); } auto calc = [&](int t, int l, int r) -> ll { if (t == 1) return (h.rquery(l, r) + MOD) % MOD * inv[l - 1] % MOD; else return (h2.rquery(l, r) + MOD) % MOD * inv[l - 1] % MOD; }; auto check = [&](int l, int r) -> bool { return calc(1, l, r) == calc(2, n - r + 1, n - l + 1); }; int cnt = 0; while (q--) { int t, l, r; cin >> t >> l >> r; auto work = [&]() -> void { int L = l + r + 1 >> 1, R = r + 1; while (R - L > 1) { int mid = L + R >> 1; if (check(l + r - mid, mid)) L = mid; else R = mid; } if ((r - l) % 2 && L == (l + r + 1) >> 1 && !check(L - 1, L)) R--; for (int LL = l + r - R, RR = R; LL >= l && RR <= r; LL--, RR++) { int pl = find(LL), pr = find(RR); if (pl == pr) continue; if (id[pl].size() < id[pr].size()) swap(pl, pr); while (!id[pr].empty()) { int v = id[pr].back(); id[pr].pop_back(); h.mod(v, (pl * p[v] % MOD) - (pr * p[v] % MOD)); h2.mod(n - v + 1, (pl * p[n - v + 1] % MOD) - (pr * p[n - v + 1] % MOD)); id[pl].pb(v); pa[v] = pl; } } }; if (t == 1) { if (check(l, r)) continue; work(); } else { int x, y; cin >> x >> y; if (l > x) swap(l, x), swap(r, y); if (r - l != y - x) { cout << "Not equal\n"; } else { ll L = (h.rquery(l, r) + MOD) % MOD; ll R = (h.rquery(x, y) + MOD) % MOD; L = L * p[x - l] % MOD; if (L == R) cout << "Equal\n"; else cout << "Unknown\n"; } } } } int main() { ZTMYACANESOCUTE; int T = 1; // cin >> T; while (T--) { solve(); } } ``` :::