The 2022 ICPC Asia Regionals Online Contest (II) 2022ICPC第二场网络赛 ABEFGJKL题解
文章目錄
- A Yet Another Remainder【費馬小定理】
- B Non-decreasing Array【線性DP】
- E An Interesting Sequence【簽到】
- F Infinity Tree 【簽到】
- G Good Permutation【排列組合,樹形結構】
- J A Game about Increasing Sequences【博弈】
- K Black and White Painting【模擬】
- L Quadruple【簡單容斥】
A Yet Another Remainder【費馬小定理】
首先明確x是無法求解的,當x大于1e6,一共有1e4個方程組,不可能求解1e6個未知數。
因為題目將x的各個十進制位拆開,并且按照不同的步長分組,因此可能是通過計算組內的值,再將各組的值拼接起來得到xmodpx\ mod\ px?mod?p的結果。
ppp都是小于100的數,modpmod\ pmod?p的結果必然都是100以內的值,因此推測modpmod\ pmod?p的結果可能會產生某種規律,比如形成循環節出現。
到這里沒有什么新的思路,因此可以翻翻書找找跟質數和模數有關的定理來輔助解題…最常見最相關的就是費馬小定理了!
費馬小定理: 如果p是一個質數,而整數a不是p的倍數,則有a(p?1)≡1a^{(p-1)}≡1a(p?1)≡1(mod p)。
費馬小定理中的ppp就是我們題目中的ppp,那么aaa可以是什么呢?題目將xxx按照10進制進行拆分,因此aaa可以是10i10^i10i,可惜發現p=5p=5p=5時10i10^i10i是5的倍數,但是!題目非常細心地將p=5p=5p=5的情況給剔除掉了!因此可以確定這個方向應該是正確的!
接下來就是利用費馬小定理給xmodpx\ mod\ px?mod?p進行分組求和了。因為10p?1≡1(modp)10^{p-1}≡1(mod\ p)10p?1≡1(mod?p),因此有102(p?1)≡(modp),103(p?1)≡(modp)...10s(p?1)≡(modp)10^{2(p-1)}≡(mod\ p),\ 10^{3(p-1)}≡(mod\ p)...\ 10^{s(p-1)}≡(mod\ p)102(p?1)≡(mod?p),?103(p?1)≡(mod?p)...?10s(p?1)≡(mod?p),其中s表示最后一項。因此可以將10k(p?1)≡1(modp)10^{k(p-1)}≡1(mod\ p)10k(p?1)≡1(mod?p)當成一組同時求解。而在第p?1p-1p?1行,給定的恰好是以p?1p-1p?1為步長的十進制位的和。
因為x=10n?1?a1+10n?2?a2+...+10n?i?ai+100?anx=10^{n-1}*a^1+10^{n-2}*a^2+...+ 10^{n-i}*a^i+10^0*a^nx=10n?1?a1+10n?2?a2+...+10n?i?ai+100?an,可知找到10s(p?1)10^{s(p-1)}10s(p?1)對應的是a數組的第n?s(p?1)n-s(p-1)n?s(p?1)項,即bp?1,n?s(p?1)b_{p-1,n-s(p-1)}bp?1,n?s(p?1)?。
將10k(p?1)+1,k=0,1,2,...,s10^{k(p-1)+1},\ k=0,1,2,...,s10k(p?1)+1,?k=0,1,2,...,s的項作為一組,將10k(p?1)+2,k=0,1,2,...,s10^{k(p-1)+2},\ k=0,1,2,...,s10k(p?1)+2,?k=0,1,2,...,s作為一組。
即將x拆解為(a1+a1+(p?1)+a1+2(p?1)+...+a1+s(p?1))?10(n?1)%(p?1))+(a2+a2+(p?1)+a2+2(p?1)+...+a2+s(p?1))?10(n?2)%(p?1))+...+(ap?1+ap?1+(p?1)+ap?1+2(p?1)+...+ap?1+s(p?1))?10(n?p+1)%(p?1))(a_1+a_{1+ (p-1)}+a_{1+2(p-1)}+...+a_{1+s(p-1)})*10^{(n-1)\%(p-1)})+\\(a_2+a_{2+ (p-1)}+a_{2+2(p-1)}+...+a_{2+s(p-1)})*10^{(n-2)\%(p-1)})+\\...+\\(a_{p-1}+a_{p-1+ (p-1)}+a_{p-1+2(p-1)}+...+a_{p-1+s(p-1)})*10^{(n-p+1)\%(p-1)})(a1?+a1+(p?1)?+a1+2(p?1)?+...+a1+s(p?1)?)?10(n?1)%(p?1))+(a2?+a2+(p?1)?+a2+2(p?1)?+...+a2+s(p?1)?)?10(n?2)%(p?1))+...+(ap?1?+ap?1+(p?1)?+ap?1+2(p?1)?+...+ap?1+s(p?1)?)?10(n?p+1)%(p?1))
用b表示為bp?1,1?10(n?1)%(p?1)+bp?1,2?10(n?2)%(p?1)+...+bp?1,p?1?10(n?p+1)%(p?1)b_{p-1,1}*10^{(n-1)\%(p-1)}+b_{p-1,2}*10^{(n-2)\%(p-1)}+...+b_{p-1,p-1}*10^{(n-p+1)\%(p-1)}bp?1,1??10(n?1)%(p?1)+bp?1,2??10(n?2)%(p?1)+...+bp?1,p?1??10(n?p+1)%(p?1)
B Non-decreasing Array【線性DP】
賽時沒過題,一直往區間dp想,復雜度是n4n^4n4的,很難優化。但是dp最簡單的就是從頭到尾推的線性dp啊…下次思路卡住就看看書吧!dp入門!
先簡單地證明一下,將一個數字刪除總是會得到更好的結果
假設現在的數組是a,b,c(c>=b>=a)a,b,c(c>=b>=a)a,b,c(c>=b>=a),三個數對答案的貢獻是(c?b)2+(b?a)2(c-b)^2+(b-a)^2(c?b)2+(b?a)2,而將中間的數刪除之后,對答案的貢獻變成(c?a)2=((c?b)+(b?a))2=(c?b)2+(b?a)2+2(c?b)(b?a)(c-a)^2=((c-b)+(b-a))^2=(c-b)^2+(b-a)^2+2(c-b)(b-a)(c?a)2=((c?b)+(b?a))2=(c?b)2+(b?a)2+2(c?b)(b?a),因為c>=b,b>=ac>=b,b>=ac>=b,b>=a,因此每刪除一個中間數,答案就相比原來增加2*中間數與前數的差*后數與前數的差。刪除一個數,答案增加如上所述,而改變一個數,其實最佳方式就是直接將其變成它的某個相鄰數,因為相鄰差變為0,對答案不再有貢獻,其實和刪除的操作是一樣的。因此第kkk次操作,就是選兩個數刪除掉。
先記錄起始的答案,接下來只有相鄰差有用,而原來的數已經沒有用了,因此直接記錄它們的相鄰差數組difdifdif即可。接下來的問題就像石子合并:一開始有n?1n-1n?1堆石子,每堆石子的數量為dif[i]dif[i]dif[i],每次選擇相鄰的兩堆石子i,ji,ji,j,答案增加2?dif[i]?dif[j]2*dif[i]*dif[j]2?dif[i]?dif[j],然后將兩堆石子合并起來。
f[i][j]f[i][j]f[i][j]表示從第一堆石子到第iii堆石子,已經合并了jjj堆石子獲得的最大答案。
接下來思考如何更新f[i][j]f[i][j]f[i][j]。只需要考慮最后第iii堆和前面多少堆連續合并在一起即可。
依次類推即可。
note: 可預處理出數組g[i][j]g[i][j]g[i][j]表示從iii到jjj連續段石子合并的答案。
#include<bits/stdc++.h> #define LL long long using namespace std; const int N = 110; LL g[N][N], f[N][N], dif[N], pre[N]; int a[N]; int main() {int n; cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];LL ans = 0;for(int i = 1; i < n; i ++) dif[i] = a[i + 1] - a[i], ans += dif[i] * dif[i], pre[i] = pre[i - 1] + dif[i];//預處理出從i到j連續段的答案貢獻n --;for(int i = 1; i < n; i ++)for(int j = i + 1; j <= n; j ++)g[i][j] = g[i][j - 1] + 2 * dif[j] * (pre[j - 1] - pre[i - 1]);for(int i = 1; i <= n; i ++)for(int j = 0; j < i; j ++)for(int k = 1; k <= i; k ++)if(j - i + k >= 0)f[i][j] = max(f[i][j], f[k - 1][j - i + k] + g[k][i]);for(int i = 1; i <= n + 1; i ++){printf("%lld\n", ans + f[n][min(i * 2, n - 1)]);}return 0; }E An Interesting Sequence【簽到】
找到第一個和k互質的數,然后一直放2323…或者3232…即可。
F Infinity Tree 【簽到】
觀察發現,第n秒樹的大小為(k+1)n(k+1)^n(k+1)n,不難推出,節點x的父親節點為(x?t?1)/k+1(x - t - 1) / k + 1(x?t?1)/k+1,其中ttt為第一個小于x的(k+1)n(k+1)^n(k+1)n,每次暴力找到第一個小于x和小于y的節點,一直往上跳即可。
G Good Permutation【排列組合,樹形結構】
考慮最簡單的情況,只有[1,n][1,n][1,n]一個區間,答案就是n!n!n!。如果[1,n][1,n][1,n]中包含333個區間限制,恰好覆蓋[1,n][1,n][1,n]整個區間,長度分別為len1,len2,len3len1,len2,len3len1,len2,len3,答案是3!?len1!?len2!?len3!3!*len1!*len2!*len3!3!?len1!?len2!?len3!。3!3!3!表示將3個限制區間在[1,n][1,n][1,n]之間進行全排列,len1!len1!len1!表示被分到第1個區間的連續數字的排列方案。但是如果區間1還有2個小區間,長度分別為len11,len12len11,len12len11,len12,這時區間1的方案數就變成2!?len11!?len122!*len11!*len122!?len11!?len12,全區間的方案數也會從3!?len1!?len2!?len3!3!*len1!*len2!*len3!3!?len1!?len2!?len3!變成3!?2!?len11!?len12?len2!?len3!3!*2!*len11!*len12*len2!*len3!3!?2!?len11!?len12?len2!?len3!。因此應該先將小區間更新,更新完小區間再用小區間更新大區間。
因為每個區間只有包含和并列關系,因此構成了樹形結構,可以給每個區間一個編號。然后排序亂搞一通獲得一棵樹結構。先利用set將區間去重,然后按照左端點從小到大,右端點從大到小排序。這樣排序可保證后面的區間必然不是前面的區間的父親,因此每次記錄上一次排序結束的區間節點preprepre,當前區間的節點為curcurcur,則curcurcur的父親就是curcurcur和preprepre的最近公共祖先,因為preprepre和curcurcur區間中間已經沒有隔著其他區間了。
#include<bits/stdc++.h> #define LL long long using namespace std; const int N = 1e6 + 10, mod = 1e9 + 7; struct Seg {int l, r, len, fa;bool operator<(const Seg &x)const{if(l == x.l) return x.r < r;return l < x.l;}bool operator=(const Seg &x)const{return l == x.l && r == x.r;} }seg[N]; set<Seg> tmp_seg; int n, m; int fac[N]; vector<int> son[N]; int dfs(int u) {int ans = 1, len = 0;for(auto v : son[u]){ans = 1ll * ans * dfs(v) % mod;len += seg[v].len;}int left = seg[u].len - len, sz = left + son[u].size();ans = 1ll * fac[sz] * ans % mod;return ans; } int main() {scanf("%d%d", &n, &m);for(int i = 1; i <= m; i ++){int l, r; scanf("%d%d", &l, &r);if(l == 1 && r == n) continue;tmp_seg.insert({l, r, r - l + 1, 0});}int cnt = 1;seg[cnt].l = 1, seg[cnt].r = n, seg[cnt].len = n;for(auto it : tmp_seg){int pre = cnt; cnt ++;seg[cnt].l = it.l, seg[cnt].r = it.r, seg[cnt].len = it.len;while(seg[pre].r < seg[cnt].r) pre = seg[pre].fa;seg[cnt].fa = pre;son[pre].push_back(cnt);}fac[0] = 1;for(int i = 1; i <= max(m, n); i ++) fac[i] = 1ll * fac[i - 1] * i % mod;printf("%d\n", dfs(1));return 0; }J A Game about Increasing Sequences【博弈】
可以發現,只有數列從左向右最長的一段遞增序列和從右向左最長的一段遞增序列有用。
考慮這兩段序列的起始元素,如果兩個元素大小相同,且兩段序列中存在一段奇數長度的序列則先手必勝。
否則:
如果大的那個元素對應的序列長度為奇數,那么先手必勝;
不然先手必然選小的那個元素,然后輪到后手選;
我們直接模擬一遍即可得出結果。
K Black and White Painting【模擬】
因為整個網格的大小只有200*200,因此考慮標記每個網格的有貢獻的邊和圓弧來統計答案。用左下角的點來表示網格。對于每個網格,直線邊有上下左右四條邊,網格內可能存在四種圓弧。因此只需要考慮邊和圓弧的所有組合情況即可。
某個網格里圓弧組合為02 或者13,則四條邊和所有圓弧都會被覆蓋失效。
若整個網格都某個正方形覆蓋,那么不管網格里的所有圓弧都會失效,枚舉網格四條邊看是否存在一條正方形邊,且不會被其他的圖形覆蓋。比如判斷網格左邊是否有貢獻,要檢查:網格左側有正方形邊,當前網格左側的網格沒有整個網格被覆蓋且沒有23形狀的圓弧。
存在兩段圓弧(除了02組合和13組合的其他圓弧組合情況),都會有1/3的圓弧貢獻
存在一段圓弧,都會有1/2的圓弧貢獻。
L Quadruple【簡單容斥】
首先不難想到處理出ICPC的前綴和來對付區間詢問,但是這樣會算多一些,因為區間外的I與區間內的CPC組成的ICPC、區間外的IC和區間內的PC組成的ICPC和區間外的ICP和區間內的C組成的ICPC都會被算進去。于是我們減掉這些即可。CPC的方案數和PC的方案數計算方法也是類似的。
#include<bits/stdc++.h> #define LL long long using namespace std; const int N = 2e6 + 10, mod = 998244353; char s[N]; int n, q, u[N], v[N]; struct Int {int a;Int(){}Int(int _a){a = _a;}Int operator + (const Int &b)const {return Int{(a + b.a) % mod};}Int operator - (const Int &b)const {return Int{(a - b.a + mod) % mod};}Int operator * (const Int &b)const {return Int{1ll * a * b.a % mod};} }I[N], C[N], P[N], IC[N], CP[N], PC[N], ICP[N], CPC[N], ICPC[N]; int get(int l, int r) {Int sicpc = ICPC[r] - ICPC[l - 1];Int spc = PC[r] - PC[l - 1] - P[l - 1] * (C[r] - C[l - 1]);Int scpc = CPC[r] - CPC[l - 1] - CP[l - 1] * (C[r] - C[l - 1]) - C[l - 1] * spc;sicpc = sicpc - scpc * I[l - 1] - IC[l - 1] * spc - ICP[l - 1] * (C[r] - C[l - 1]);return sicpc.a; } int main() {scanf("%d%d", &n, &q);scanf("%s", s + 1);int x, a, b, p, ans = 0;scanf("%d%d%d%d", &x, &a, &b, &p);for(int i = 1; i <= n; i ++){ICPC[i] = ICPC[i - 1] + ICP[i - 1] * (s[i] == 'C');ICP[i] = ICP[i - 1] + IC[i - 1] * (s[i] == 'P'), CPC[i] = CPC[i - 1] + CP[i - 1] * (s[i] == 'C');IC[i] = IC[i - 1] + I[i - 1] * (s[i] == 'C'), CP[i] = CP[i - 1] + C[i - 1] * (s[i] == 'P'), PC[i] = PC[i - 1] + P[i - 1] * (s[i] == 'C');I[i] = I[i - 1] + (s[i] == 'I'), C[i] = C[i - 1] + (s[i] == 'C'), P[i] = P[i - 1] + (s[i] == 'P');}for(int i = 1; i <= n; i ++) u[i] = x = (1ll * a * x + b) % p, u[i] = u[i] % n + 1;for(int i = 1; i <= n; i ++) v[i] = x = (1ll * a * x + b) % p, v[i] = v[i] % n + 1;for(int i = 1; i <= n; i ++) ans = (ans + get(min(u[i], v[i]), max(u[i], v[i]))) % mod;printf("%d\n", ans);return 0; }總結
以上是生活随笔為你收集整理的The 2022 ICPC Asia Regionals Online Contest (II) 2022ICPC第二场网络赛 ABEFGJKL题解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大学生职业生涯规划书PPT模板
- 下一篇: SAP业务事务代码