12月训练
300iq contest 3
F
题意:给定$n$个怪的血量,先手攻击力$a$,后手攻击力$b$,先手每次任选一个攻击,后手只选编号最小的,求先手最多杀多少只
先求出$L_i$表示杀第$i$只怪时可以节省的攻击次数,$R_i$表示不杀时可以节省的攻击次数
初始先手节省次数为$1$,那么问题转化为每个位置选择$L_i$或$R_i$,使得每个前缀和均非负,那么可以先初始化每个位置都选$L$,然后用堆贪心撤销
#include <bits/stdc++.h> using namespace std; const int N = 3e5+10; int n, a, b, h[N], L[N], R[N]; int main() { scanf("%d%d%d", &n, &a, &b); for (int i=1; i<=n; ++i) scanf("%d", &h[i]); for (int i=1; i<=n; ++i) { R[i] = (h[i]+b-1)/b; int t = h[i]-(R[i]-1)*b; L[i] = R[i]-1-(t+a-1)/a; } int ans = n; int64_t s = 1; priority_queue<int64_t> q; for (int i=1; i<=n; ++i) { s += L[i]; q.push(R[i]-L[i]); while (s<0) { s += q.top(); q.pop(); --ans; } } printf("%d\n", ans); }
J
题意:给定图,每条边赋一个$[0,4]$的权值,要求每个点邻接边权值和是$5$的倍数,求方案数
对于一个连通块,答案就是$5^{m-basis}$,如果是树的话,方案数只能为$1$,$basis=n-1$
假设有环的话,如果是二分图的话,每个环交替填$-1,1$,一定合法,不影响$basis$
如果是树上连一个奇环的话,方案数是$1$,$basis=n$
由于$basis$一定是不超过$n$的,所以二分图答案$5^{m-n+1}$,否则$5^{m-n}$
E
题意:$n$堆石子,第$i$堆$a_i$个,两人轮流取,每次至多取$x$个,不能取则输,对于每个$x$输出谁赢
bash博弈,等价于求$a_i \space mod (x+1)$的异或和
枚举每个二进制位,转化为求$\sum\limits_{i=1}^n \lfloor\frac{a_i-\lfloor\frac{a_i}{x}\rfloor x}{2^d}\rfloor$的奇偶性
枚举$a_i$值很小,可以枚举$t=\lfloor\frac{a_i}{x}\rfloor$,那么等价于求$\sum\limits_{t\le i<i+t}\lfloor\frac{i-t}{2^d}\rfloor$
$\lfloor\frac{i-t}{2^d}\rfloor=\lfloor\frac{i}{2^d}\rfloor - \lfloor\frac{t}{2^d}\rfloor - [i\space mod 2^d<t\space mod 2^d]$
第三项是个二维偏序,用树状数组的话总复杂度是$O(n\log^3 n)$过不去
可以发现$i \space mod 2^d$是循环的,预处理每块的前缀和就可以$O(n\log^2 n)$
#include <bits/stdc++.h> using namespace std; const int N = 5e5+10, M = 19; bool cnt[N], sg[N], f[N], sum[N], g[N], h[N]; int main() { ios::sync_with_stdio(0); cin.tie(0); int n; cin>>n; for (int i=1; i<=n; ++i) { int t; cin>>t; cnt[t] ^= 1; } for (int i=1; i<=n; ++i) sum[i] = sum[i-1]^cnt[i]; for (int d=0,num=1; num<=n; num<<=1,++d) { for (int i=1; i<=n; ++i) f[i] = g[i] = h[i] = 0; for (int i=1; i<=n; ++i) { f[i] = f[i-1]^i>>d&cnt[i]; g[i] = cnt[i]; if (i%num) g[i] ^= g[i-1]; h[i] = g[i]; if (i>num) h[i] ^= h[i-num]; } auto gao = [&](int i, int val) { if (i<=val) return g[i]; bool ans = h[val+(i-val>>d<<d)]; if (i%num<val) ans ^= g[i]; return ans; }; for (int x=d+1; x<=n+1; ++x) if (!sg[x]) { int tot = 0; for (int t=0; t<=n; t+=x) { int lx = max(t,1), rx = min(n,t+x-1); if (lx<=rx) { tot ^= f[rx]^f[lx-1]; tot ^= (sum[rx]^sum[lx-1])&(t>>d); int val = t%num-1; if (val>=0) { tot ^= gao(rx,val); if (lx>1) tot ^= gao(lx-1,val); } } } if (tot&1) sg[x] = 1; } } for (int x=2; x<=n+1; ++x) cout<<(sg[x]?"Alice":"Bob")<<" \n"[x==n+1]; }
gym102798J
题意:$n$堆石子,两人轮流取,每次要么选一个最小的黑堆取任意正数颗,要么选一个白堆取任意正数颗,不能取则输,求多少种染色方案使得后手必胜
白堆就是nim游戏,对于黑堆,打个表可以发现,去除掉最大值后,如果最小值$x$个数为奇$sg=x-1$,否则$sg=x$
枚举最小值以及最小值的出现次数,那么转化为求一个后缀的子集异或和为$s$的方案,倒序添点用线性基判断即可
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10, P = 1e9+7; int n, pw[N], fac[N], ifac[N]; int64_t sg[N], a[N], suf[N], b[100]; int qpow(int a, int n) { int ans = 1; for (; n; n>>=1,a=a*(int64_t)a%P) if (n&1) ans=ans*(int64_t)a%P; return ans; } int C(int n, int m) { return fac[n]*(int64_t)ifac[m]%P*ifac[n-m]%P; } int main() { scanf("%d", &n); pw[0] = fac[0] = 1; for (int i=1; i<=n; ++i) { pw[i] = pw[i-1]*2%P; fac[i] = fac[i-1]*(int64_t)i%P; } ifac[n] = qpow(fac[n],P-2); for (int i=n-1; i>=0; --i) ifac[i] = ifac[i+1]*(i+1ll)%P; for (int i=1; i<=n; ++i) scanf("%lld", &a[i]); sort(a+1,a+1+n); for (int i=1; i<=n; ++i) sg[i] = sg[i-1]^a[i]; for (int i=n; i; --i) suf[i] = suf[i+1]^a[i]; int ans = !sg[n]; auto calc = [&](int64_t s, int pos) { static int now = n; while (now>=pos) { int64_t t = a[now]; for (int i=1; i<=*b; ++i) t = min(t, t^b[i]); if (t) b[++*b] = t; --now; } int ans = suf[pos]==s; for (int i=1; i<=*b; ++i) s = min(s, s^b[i]); if (s) return 0; return pw[n-pos+1-*b]-ans; }; for (int i=n; i; --i) { int j = i; while (j-1>=1&&a[j-1]==a[i]) --j; for (int k=1; k<=i-j+1; ++k) { int64_t w = k&1?a[i]-1:a[i], d = (i-j+1-k)&1?a[i]:0; ans = (ans+C(i-j+1,k)*(int64_t)calc(sg[j-1]^d^w,i+1))%P; w = k&1?a[i]:a[i]-1; if (w==(sg[j-1]^d^suf[i+1])) ans = (ans+C(i-j+1,k))%P; } i = j; } printf("%d\n", ans); }
XXI Open Cup. Grand Prix of Korea
J
题意:给定操作序列,点(0,0)有个障碍,往障碍上走的操作无效,$q$次询问,给定起点,求操作后终点坐标
假设不会碰到障碍的话,横纵坐标求个和即可,如果会碰到的话,预处理出每个后缀碰到障碍时的终点即可
#include <bits/stdc++.h> using namespace std; const int N = 3e5+10; int n, q; pair<int,int> a[N]; char s[N]; pair<int,int> get(char c) { if (c=='L') return {1,0}; if (c=='R') return {-1,0}; if (c=='U') return {0,-1}; return {0,1}; } void Move(int &x, int &y, char c) { if (c=='L') --x; if (c=='R') ++x; if (c=='U') ++y; if (c=='D') --y; } int main() { scanf("%d%s%d", &n, s+1, &q); map<pair<int,int>,int> vis; int x = 0, y = 0; vis[{0,0}] = n+1; a[n] = get(s[n]); Move(x,y,s[n]); vis[{x,y}] = n; for (int i=n-1; i; --i) { if (s[i]==s[i+1]) a[i] = a[i+1]; else { auto u = get(s[i]); pair<int,int> v{x+u.first,y+u.second}; if (vis.count(v)) a[i] = a[vis[v]-1]; else a[i] = v; } Move(x,y,s[i]); vis[{x,y}] = i; } vis.clear(); x = y = 0; for (int i=1; i<=n; ++i) { Move(x,y,s[i]); if (!vis.count({x,y})) vis[{x,y}] = i; } while (q--) { int X, Y; scanf("%d%d", &X, &Y); if (!vis.count({-X,-Y})) printf("%d %d\n", X+x, Y+y); else { int t = vis[{-X,-Y}]; printf("%d %d\n", a[t].first, a[t].second); } } }
H
题意:$n$种数,给定每种数个数,每次选一个子集替换为$mex$,求最后剩一个数时的最大值
特判只有一个数的情况,否则至少要有一次取mex操作,所以如果想要得到$x$,那么就要构造出$0,1,...,x-1$,可以二分答案判断
#include <bits/stdc++.h> using namespace std; const int N = 4e6+10; int n, c[N]; int64_t d[N]; int chk(int x) { for (int i=0; i<x; ++i) d[i] = c[i]; for (int i=N-1; i>=x; --i) d[0] += c[i]; int64_t sum = 0; for (int i=x-1; i>0; --i) { if (d[i]+sum>1) d[0] += d[i]+sum-1; else if (d[i]+sum<=0) sum += d[i]+sum-1; if (sum<-1e15) return 0; } return d[0]+sum>=1; } int main() { scanf("%d", &n); for (int i=0; i<n; ++i) scanf("%d", &c[i]); int64_t sum = 0; for (int i=0; i<n; ++i) sum += c[i]; if (sum==1) { int ans = 1; for (int i=2; i<n; ++i) if (c[i]) ans = i; printf("%d\n", ans); return 0; } int l = 2, r = N-1, ans = 1; while (l<=r) { int mid = (l+r)/2; if (chk(mid)) ans=mid,l=mid+1; else r=mid-1; } printf("%d\n", ans); }
D
可以发现一条边(u,v)的权值一定要不小于u到v所有路径的最小值,可以用并查集求
#include <bits/stdc++.h> using namespace std; const int N = 6e5+10; int n, m, fa[N], sz[N], val[N]; struct _ {int u,v,w;} e[N]; int Find(int x) {return fa[x]?fa[x]=Find(fa[x]):x;} int main() { scanf("%d%d", &n, &m); for (int i=1; i<=n; ++i) sz[i] = 1; for (int i=1; i<=m; ++i) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w); sort(e+1,e+1+m,[](_ a,_ b){return a.w>b.w;}); int64_t ans = 0; int tot = n; for (int i=1,j; i<=m; i=j) { j = i; while (j<=m&&e[j].w==e[i].w) { int u = Find(e[j].u), v = Find(e[j].v); if (u==v&&e[j].w!=val[u]) return puts("-1"),0; ++j; } for (int k=i; k<j; ++k) { int u = Find(e[k].u), v = Find(e[k].v); if (u!=v) { fa[u] = fa[v] = ++tot; val[tot] = e[i].w; sz[tot] = sz[u]+sz[v]; ans += sz[u]*(int64_t)sz[v]*e[k].w; } } } int now = 0; for (int i=1; i<=2*n; ++i) if (Find(i)==i) { ans += now*(int64_t)sz[Find(i)]; now += sz[Find(i)]; } printf("%lld\n", ans); }
G
题意:给定长$N$的串$S$,求长$N$的$T$,使得$S$和$T$最长公共子序列长度为$N-K$
gym102769H
题意:一个合法序列要求每个数范围$[1,n]$,假设前缀最大值$p_i$,要求$p_i-p_{i-1}\le 1$。求所有合法序列中每种元素出现次数平方和
枚举$k$的第一次出现位置,那么前缀方案是一个是一个斯特林数,后缀贡献是一个合法序列中选两个$k$的方案
dp即可
#include <bits/stdc++.h> using namespace std; const int N = 3e3+10; int pre[N][N], suf[N][N], ans[N]; int main() { int T; scanf("%d", &T); for (int clk=1; clk<=T; ++clk) { int n, m; scanf("%d%d", &n, &m); pre[0][0] = 1; for (int i=1; i<=n; ++i) for (int j=1; j<=i; ++j) pre[i][j] = (pre[i-1][j]*(int64_t)j+pre[i-1][j-1])%m; for (int i=1; i<=n; ++i) ans[i] = 0, suf[0][i] = 1; for (int i=1; i<=n; ++i) for (int j=1; j<=n; ++j) suf[i][j] = (suf[i-1][j]*(j-1ll)+suf[i-1][min(n,j+1)])%m; //pre[i][j] = 前i个数,最大值为j的方案 //suf[i][j] = 长i的序列,第一个数可以填>=j的方案 for (int i=1; i<=n; ++i) ans[i] = 0; for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) { //枚举数j的第一次出现位置i //两个k都选位置i时,贡献suf[n-i][j+1] //两个k都选除i以外的相同位置,贡献(n-i)suf[n-i-1][j+1] //一个选i一个不选i,贡献2(n-i)suf[n-i-1][j+1] //两个都不选i且不相同,贡献(n-i)(n-i-1)suf[n-i-2][j+1] int w = suf[n-i][min(n,j+1)]; if (n-i>=1) w = (w+(n-i)*3ll*suf[n-i-1][min(n,j+1)])%m; if (n-i>=2) w = (w+(n-i)*(n-i-1ll)*suf[n-i-2][min(n,j+1)])%m; ans[j] = (ans[j]+pre[i-1][j-1]*(int64_t)w)%m; } } printf("Case #%d: \n", clk); for (int i=1; i<=n; ++i) printf("%d%c", ans[i], " \n"[i==n]); } }
gym101991K
题意:给定$n,k,l,r$,求有多少个序列,满足每个元素范围$[l,r]$,且和为$3$的倍数的区间恰好$k$个
考虑前缀和序列$s$,一个合法区间$[l,r]$等价于$s_{l-1}=s_{r}$,假设$s$中有$x$个0,$y$个$1$,$z$个$2$,那么合法区间数就是$x(x-1)/2+y(y-1)/2+z(z-1)/2$
所以如果$n>\sqrt{6k}$的话直接无解,否则$O(n^3)dp$一下即可
#include <bits/stdc++.h> using namespace std; const int N = 255, P = 1e9+7; int n, K, l, r, cnt[3]; int dp[N][N][N][3]; void add(int &a, int64_t b) {a=(a+b)%P;} int main() { freopen("khoshaf.in","r",stdin); int T; scanf("%d", &T); while (T--) { scanf("%d%d%d%d", &n, &K, &l, &r); if (((n/3)*(n/3)*3-n)/2>K) {puts("0");continue;} auto calc = [](int l, int r, int x) { int ans = 0; while (l%3!=x%3) ++l; while (r>=0&&r%3!=x%3) --r; if (l>r) return 0; return (r-l)/3+1; }; for (int i=0; i<3; ++i) cnt[i] = calc(l,r,i); for (int i=0; i<=n+1; ++i) for (int j=0; j<=n+1; ++j) for (int k=0; k<=n+1; ++k) for (int z=0; z<3; ++z) dp[i][j][k][z] = 0; dp[0][1][0][0] = 1; int ans = 0; for (int i=0; i<=n; ++i) { for (int j=0; j<=i+1; ++j) { for (int k=0; j+k<=i+1; ++k) { int t = i+1-j-k; for (int z=0; z<3; ++z) { int ret = dp[i][j][k][z]; if (!ret) continue; if (i==n) { if (j*(j-1)/2+k*(k-1)/2+t*(t-1)/2==K) add(ans,ret); continue; } for (int num=0; num<3; ++num) { int u = (z+num)%3; add(dp[i+1][j+(u==0)][k+(u==1)][u],ret*(int64_t)cnt[num]); } } } } } printf("%d\n", ans); } }
2020 ccpc mianyang
B
题意:给定左前视图和$k$个位置的高度,求有多少种摆法
左前视图相当于限制了斜着一排的最大值,可以枚举斜着的每一排dp
#include <bits/stdc++.h> using namespace std; const int N = 2e5+10, P = 1e9+7; int n, m, k, a[N], R[N], dp[N][2], cnt[N], fac[N], ifac[N]; int qpow(int a, int n) { int ans = 1; for (; n; n>>=1,a=a*(int64_t)a%P) if (n&1) ans=ans*(int64_t)a%P; return ans; } int C(int n, int m) { return fac[n]*(int64_t)ifac[m]%P*ifac[n-m]%P; } void add(int &a, int64_t b) {a=(a+b)%P;} int main() { fac[0] = 1; for (int i=1; i<N; ++i) fac[i] = fac[i-1]*(int64_t)i%P; ifac[N-1] = qpow(fac[N-1], P-2); for (int i=N-2; i>=0; --i) ifac[i] = ifac[i+1]*(i+1ll)%P; int T; scanf("%d", &T); for (int clk=1; clk<=T; ++clk) { scanf("%d%d%d", &n, &m, &k); for (int i=1; i<=n+m; ++i) { scanf("%d", &a[i]), R[i] = 0; cnt[i] = min(i-1,n)-max(i-m,1)+1; dp[i][0] = dp[i][1] = 0; } for (int i=1; i<=k; ++i) { int x, y, h; scanf("%d%d%d", &x, &y, &h); R[x+y] = max(R[x+y], h); R[x+y-1] = max(R[x+y-1], h); --cnt[x+y]; } int ok = 1; for (int i=1; i<=n+m; ++i) if (R[i]>a[i]) ok = 0; if (!ok) { printf("Case #%d: 0\n", clk); continue; } dp[1][R[1]==a[1]] = 1; for (int i=2; i<=n+m; ++i) { if (a[i]==a[i-1]) { if (cnt[i]) add(dp[i][1],(dp[i-1][0]+dp[i-1][1])*(int64_t)(qpow(a[i],cnt[i])-qpow(a[i]-1,cnt[i]))); add(dp[i][0],dp[i-1][1]*(int64_t)qpow(a[i]-1,cnt[i])); } else if (a[i]<a[i-1]) { if (cnt[i]) add(dp[i][1],dp[i-1][1]*(int64_t)(qpow(a[i],cnt[i])-qpow(a[i]-1,cnt[i]))); add(dp[i][0],dp[i-1][1]*(int64_t)qpow(a[i]-1,cnt[i])); } else { if (cnt[i]) add(dp[i][0],(dp[i-1][0]+dp[i-1][1])*(int64_t)(qpow(a[i-1],cnt[i])-qpow(a[i-1]-1,cnt[i]))); add(dp[i][0],dp[i-1][1]*(int64_t)qpow(a[i-1]-1,cnt[i])); } if (R[i]==a[i]) add(dp[i][1],dp[i][0]), dp[i][0] = 0; } if (dp[n+m][1]<0) dp[n+m][1] += P; printf("Case #%d: %d\n", clk, dp[n+m][1]); } }
C
题意:定义了字典树插入询问操作,给定一些询问的结果,求字典树最少多少个节点
分类讨论题,值相同的询问求一下lca,然后每个点贪心往上缩
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10; char s[N]; int n, tot, ch[N][26], val[N], fa[N], dep[N], vis[N], c[N], sz[N]; pair<int,int> a[N]; int lca(int x, int y) { if (!x||!y) return x+y; while (dep[x]!=dep[y]) { if (dep[x]<dep[y]) y=fa[y]; else x=fa[x]; } while (x!=y) x=fa[x],y=fa[y]; return x; } int ans; int chk(int x) { int w = vis[x]; for (int i=0; i<26; ++i) if (ch[x][i]) w += chk(ch[x][i]); return w; } void dfs1(int x) { sz[x] = vis[x]; for (int i=0; i<26; ++i) if (ch[x][i]) { dfs1(ch[x][i]); sz[x] += sz[ch[x][i]]; } } void dfs(int x) { int ban = 0; for (int i=0; i<26; ++i) if (ch[x][i]) { if (sz[ch[x][i]]>1) ban = 1; if (vis[x]&&sz[ch[x][i]]) ban = 1; } if (!ban) { if (vis[x]) { ++ans; return; } int cnt = 0; for (int i=0; i<26; ++i) cnt += sz[ch[x][i]]; ans += cnt; return; } ++ans; int f = !vis[x]; for (int i=0; i<26; ++i) if (ch[x][i]) { if (!sz[ch[x][i]]) continue; if (sz[ch[x][i]]==1) { if (f) f = 0; else ++ans; continue; } dfs(ch[x][i]); } } int main() { int T; scanf("%d", &T); for (int clk=1; clk<=T; ++clk) { scanf("%d", &n); for (int i=1; i<=tot; ++i) { fa[i] = dep[i] = vis[i] = c[i] = val[i] = sz[i] = 0; memset(ch[i],0,sizeof ch[i]); } tot = 1; int ok = 1; for (int i=1; i<=n; ++i) { int x; scanf("%s%d", s+1, &x); int m = strlen(s+1), now = 1; for (int j=1; j<=m; ++j) { if (!ch[now][s[j]-'a']) { ++tot; fa[tot] = now; dep[tot] = dep[now]+1; ch[now][s[j]-'a'] = tot; } now = ch[now][s[j]-'a']; } if (val[now]&&val[now]!=x) ok = 0; else val[now] = x; a[i] = {x,now}; } if (!ok) { printf("Case #%d: -1\n", clk); continue; } sort(a+1,a+1+n); for (int i=1; i<=n; ++i) { int j = i; while (j+1<=n&&a[j+1].first==a[j].first) ++j; int l = 0; for (int k=i; k<=j; ++k) l = lca(l,a[k].second); for (int k=i; k<=j; ++k) { int t = a[k].second; if (t==l) continue; while (fa[t]!=l) t = fa[t]; c[t] = a[i].first; } if (vis[l]) ok = 0; vis[l] = 1; i = j; } for (int i=1; i<=n; ++i) { int t = a[i].second; while (t!=1) { if (c[t]&&c[t]!=a[i].first) ok = 0; t = fa[t]; } } if (!ok) { printf("Case #%d: -1\n", clk); continue; } ans = 0; dfs1(1); dfs(1); printf("Case #%d: %d\n", clk, ans); } }
E
分层图bfs
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10, INF = 0x3f3f3f3f; int n, m, k, deg[N], d[N][52]; vector<int> g[N], f[N]; queue<pair<int,int>> q; int main() { int T; scanf("%d", &T); for (int clk=1; clk<=T; ++clk) { scanf("%d%d%d", &n, &m, &k); for (int i=1; i<=n; ++i) { g[i].clear(), f[i].clear(); deg[i] = 0; memset(d[i],0x3f,sizeof d[i]); } for (int i=1; i<=m; ++i) { int u, v; scanf("%d%d", &u, &v); g[v].push_back(u); f[u].push_back(v); ++deg[u]; } auto gao = [&](int id, int l, int w) { for (int t=l; t>=0; --t) { if (d[id][t]==INF) d[id][t]=w,q.push({id,t}); else break; } }; gao(n,k,0); auto expand = [&](int id, int l) { int w = d[id][l]+1; if (l) { for (int t:g[id]) gao(t,l-1,w); for (int t:f[id]) gao(t,l-1,w); } else { if (!deg[id]&&d[id][k]>w) gao(id,k,w); for (int t:g[id]) if (!--deg[t]&&d[t][k]>w) gao(t,k,w); } }; while (q.size()) { auto u = q.front(); q.pop(); expand(u.first,u.second); } printf("Case #%d:\n", clk); for (int i=1; i<=n; ++i) printf("%d\n", d[i][0]==INF?-1:d[i][0]); } }
H
答案就等于$\sum\limits_{\substack{|x|+|y|= d_{01} \\ |xx|+|yy|=d_{02}}} [|x-xx|+|y-yy|=d_{12}]$
然后暴力分类讨论去掉绝对值即可,可以利用对称性简化一些讨论
#include <bits/stdc++.h> using namespace std; int a,b,c; int64_t s0(int l, int r) { if (l>r) return 0; return r-l+1; } int64_t s1(int l, int r) { if (l>r) return 0; return (l+r)*(r-l+1ll)/2; } int gao(int64_t x1, int64_t x2) { int ans = 0; int64_t y1 = a-abs(x1), y2 = b-abs(x2), w = c-abs(x2-x1); if (y1==0&&y2==0) { if (w==0) ++ans; } else if (y1==0) { if (y2==w) ans += 2; } else if (y2==0) { if (y1==w) ans += 2; } else { if (abs(y2-y1)==w) ans += 2; if (abs(y2+y1)==w) ans += 2; } return ans; } int64_t calc0(int n, int m) { int64_t ans = 0; if ((b+a-c)%2==0) { int t = (b+a-c)/2; if (1<=t&&t<=m) ans += s0(max(1,t),n); if (1<=t&&t<=n) ans += s0(t+1,m); } if (-b+a==c) { ans += s0(m,min(n,a-b))*m; ans += s1(1,min({n,a-b,m-1})); ans += s0(max({1,a-b+1,m}),n)*(m-b+a); ans -= s1(max({1,a-b+1,m}),n); ans += s0(max(1,a-b+1),min(n,m-1))*(a-b); } if (b-a==c) { ans += s0(max(1,m+a-b),n)*m; ans -= s1(max(1,m+a-b),n); ans += s0(1,min(m+a-b-1,n))*(b-a); } if ((b-a-c)%2==0) { int t = (b-a-c)/2; ans += s0(max({1-t,m,m-b+a}),min(n,m-t)); if (b-a>=0&&t<=0) ans += s0(max(1,1-t),min(m-1,n)); if (t<=b-a&&b-a<0) ans += s0(max(1,1-t),min(n,m-b+a-1)); } if ((c+b-a)%2==0) { int t = (c+b-a)/2; if (b-a>=0&&t>=1+b-a) ans += s0(1,min(n,m-t)); if (b-a<0&&t>=1) ans += s0(1,min(n,m-t)); } return ans; } int64_t calc3(int n, int m) { int64_t ans = 0; if (a+b==c) ans += n*(b-1ll); if ((c-b+a)%2==0) { int t = (c-b+a)/2; if (t>=a-1&&t>=1&&t<=n) ans += s0(-b+1,-1); if (t<a-1&&t>=1&&t<=n) ans += s0(a-t-b,-1); } if ((a-c-b)%2==0) { int t = (a-b-c)/2; if (-b+1<=t&&t<0) ans += s0(1,min(n,a-b)); if (-b+1<=t) ans += s0(max(1,a-b+1),min(n,a-b-1-t)); } return ans; } int64_t calc1() { if (a<=1||b<=1) return 0; return 4*calc0(a-1,b-1); } int64_t calc2() { if (a<=1||b<=1) return 0; return 4*calc3(a-1,-b+1); } int64_t calc5(int x) { int64_t ans = gao(x,-b)+gao(x,b)+gao(x,0); if (x==0) { if (a==0) { if (b==c) ans += 4ll*(b-1); } else { if (a+b==c) ans += 4ll*(b-1); if ((a-b-c)%2==0) { int t = (a-b-c)/2; if (t>=-b+1&&t<0&&t<=a-b) ans += 2; } if (b-a==c) { ans += 2*s0(max(-b+1,a-b+1),-1); ans += 2*s0(1,min(b-1,b-a)); } if ((c-a+b)%2==0) { int t = (c-a+b)/2; if (b-t<a&&t>=1&&t<b) ans += 2; } } return ans; } if (x==a) { if (b==c-a) ans += 2*(b-1); if ((b-c+a)%2==0) { int t = (b-c+a)/2; if (t>=1&&t<b&&t<=a) ans += 2; } if (b==c+a) ans += 2*s0(max(1,a+1),b-1); return ans; } if ((c-a-b)%2==0) { int t = (c-a-b)/2; if (t>=-a&&t>=-b+1&&t<0) ans += 2; } if (b==c+a) ans += 2*s0(-b+1,min(-a-1,-1)); if (b==c-a) ans += 2*(b-1); return ans; } int64_t calc4() { if (a==0&&b==0) return gao(0,0); int64_t ans = calc5(0); if (a==0) return ans; ans += calc5(-a); ans += calc5(a); swap(a,b); ans += calc5(-a); ans += calc5(a); ans += calc5(0); swap(a,b); for (int x:{-a,0,a}) for (int y:{-b,0,b}) ans -= gao(x,y); return ans; } int main() { int T; scanf("%d", &T); for (int clk=1; clk<=T; ++clk) { scanf("%d%d%d", &a, &b, &c); if (a>b) swap(a,b); int64_t ans=calc1()+calc2()+calc4(); printf("Case #%d: %lld\n", clk, ans); } }
L
题意:给定$n$种数的大小和个数,每种数都是$2$的幂,求能得到多少种和
先排序,构成和序列连续的连续段可以合并,然后答案就是每段的乘积
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10, P = 1e9+7; int n; pair<int,int> a[N]; int qpow(int a, int n) { int ans = 1; for (; n; n>>=1,a=a*(int64_t)a%P) if (n&1) ans=ans*(int64_t)a%P; return ans; } int main() { int T; scanf("%d", &T); for (int clk=1; clk<=T; ++clk) { scanf("%d", &n); for (int i=0; i<n; ++i) scanf("%d%d", &a[i].first, &a[i].second); sort(a, a+n); int ans = 1; for (int i=0; i<n; ++i) { int j = i; int64_t sum = a[i].second+1, ret = a[i].second*(int64_t)qpow(2,a[i].first)%P; int pre = a[i].first; while (j+1<n&&a[j+1].first<=pre+59) { if (sum>=(1ll<<(a[j+1].first-pre))) { ++j; sum >>= a[j].first-pre; sum += a[j].second; pre = a[j].first; ret = (ret+a[j].second*(int64_t)qpow(2,a[j].first))%P; } else break; } ans = ans*(ret*qpow(qpow(2,a[i].first),P-2)%P+1)%P; i = j; } printf("Case #%d: %d\n", clk, ans); } }
2014 icpc mudanjiang
B
题意:给定树,求两个点,使得每个点到这两点距离最小值的最大值最小
由于距每个点距离最远的点一定是两直径端点之一,考虑二分答案$x$,把两个点放在距直径$x$位置检验即可
#include <bits/stdc++.h> using namespace std; const int N = 2e5+10, INF = 1e9; int n, fa[N], vis[N]; vector<int> g[N]; int main() { int T; scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i=1; i<=n; ++i) g[i].clear(); for (int i=1; i<n; ++i) { int u, v; scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } auto bfs = [&](int x) { for (int i=1; i<=n; ++i) vis[i] = 0; queue<int> q; vis[x] = 1, q.push(x); while (q.size()) { x = q.front(); q.pop(); for (int y:g[x]) if (!vis[y]) { vis[y] = 1, fa[y] = x; q.push(y); } } return x; }; int u = bfs(1), v = bfs(u); vector<int> a; while (v!=u) a.push_back(v), v = fa[v]; a.push_back(u); int l = 0, r = a.size()-1, ans, ans_x, ans_y; auto chk = [&](int x) { ans_x = a[x], ans_y = a[a.size()-1-x]; if (ans_y==ans_x) ans_y = ans_x==1?2:1; queue<int> q; for (int i=1; i<=n; ++i) vis[i] = INF; q.push(ans_x), q.push(ans_y); vis[ans_x] = vis[ans_y] = 0; int d = x; while (q.size()) { x = q.front(); q.pop(); for (int y:g[x]) if (vis[y]==INF) vis[y] = vis[x]+1, q.push(y); } return vis[x]<=d; }; while (l<=r) { int mid = (l+r)/2; if (chk(mid)) ans=mid,r=mid-1; else l=mid+1; } chk(ans); printf("%d %d %d\n", ans, ans_x, ans_y); } }
C
E
题意:$n\times n$格子,求构造路径使得至少转弯$n(n-1)-1$次
暴力跑一下,如果发现一定可以构造出起点左上终点右上的情况,当$n+2$时可以在外围蛇形绕一圈,这样就能构造出所有奇数情况
当$n$为偶数时,不断上下蛇形交替走可以构造出$n(n-1)$个转弯
#include <bits/stdc++.h> using namespace std; int n, a[555][555], b[555][555]; void dfs(int x1, int y1, int x2, int y2, int d, int s) { if (d==1) { a[x1][y1] = ++s; return; } for (int i=x1; i+1<x1+d; i+=2) { a[i][y1] = ++s; a[i][y1+1] = ++s; a[i+1][y1+1] = ++s; a[i+1][y1] = ++s; } a[x1+d-1][y1] = ++s; a[x1+d-1][y1+1] = ++s; for (int i=y1+2; i+1<y1+d; i+=2) { a[x1+d-1][i] = ++s; a[x1+d-2][i] = ++s; a[x1+d-2][i+1] = ++s; a[x1+d-1][i+1] = ++s; } a[x1+d-1][y1+d-1] = ++s; a[x1+d-2][y1+d-1] = ++s; dfs(x1,y1+2,x1+d-3,y1+d-1,d-2,s); for (int i=x1; i<=x1+d-3; ++i) for (int j=y1+2; j<=y1+d-1; ++j) b[i][j] = a[i][j]; for (int i=x1; i<=x1+d-3; ++i) for (int j=y1+2; j<=y1+d-1; ++j) a[x1+j-y1-2][y1+d-i+x1-1] = 2*s+(d-2)*(d-2)-b[i][j]+1; } int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); if (n&1) dfs(1,1,n,n,n,0); else { int now = 0; for (int j=1; j+1<=n; j+=4) { int i = 1; for (; i+3<=n; i+=2) { a[i][j] = ++now; a[i][j+1] = ++now; a[i+1][j+1] = ++now; a[i+1][j] = ++now; } a[i][j] = ++now; a[i+1][j] = ++now; a[i+1][j+1] = ++now; a[i][j+1] = ++now; a[i][j+2] = ++now; a[i+1][j+2] = ++now; a[i+1][j+3] = ++now; a[i][j+3] = ++now; for (--i; i>=2; i-=2) { a[i][j+3] = ++now; a[i][j+2] = ++now; a[i-1][j+2] = ++now; a[i-1][j+3] = ++now; } } } for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) printf("%d ", a[i][j]); puts(""); } } }
F
题意:给定树,给定每个点取值范围,求多少种方案使得相邻点权值互质
考虑树形$dp$,${dp}_{x,i}$表示点$x$取值$i$时,子树$x$的分配方案
容易得到转移${dp}_{x,i}=\prod\sum {dp}_{y,j}[gcd(i,j)=1]$,其中$y$是$x$的儿子
反演一下就有${dp}_{x,i}=\prod \sum\limits_{d|i}\mu(d)\sum\limits_{d|j} {dp}_{y,j}$
这样就可以$O(nM\log M)$求出每个点子树的$dp$值,然后再换根$dp$即可
G
题意:给定圆和两个点$P_0,P_1$,求构造圆内一个点$P_2$满足坐标为整数且三角形$P_0P_1P_2$面积$2$倍是$S$
H
题意:给定一个串,求每个$key$对应的$value$是什么
把每个$key$的路径hash一下即可
#include <bits/stdc++.h> using namespace std; const int N = 1e6+10; const int P = 876756319, B = 9999991; int tot,nxt[N]; char s[N],t[N]; map<string,int> mp; map<int,pair<int,int>> ans; int ID(string s) { if (mp.count(s)) return mp[s]; int t = mp.size()+1; return mp[s] = t; } void build(int l, int r, int v) { if (r-l+1<=5) return; int pos = l+1; while (s[pos]!='"') ++pos; int posl = pos+2, posr = posl+1; if (s[posl]=='{') posr = nxt[posl]; else { while (s[posr]!='"') ++posr; } if (s[posr+1]==',') build(posr+2,r,v); v = ((int64_t)v*B+ID(string(s+l,s+pos+1)))%P; ans[v] = {posl,posr}; if (s[posl]=='{') build(posl+1,posr-1,v); } int main() { int T; scanf("%d", &T); while (T--) { int q; scanf("%s%d", s+1, &q); int n = strlen(s+1); mp.clear(), ans.clear(); vector<int> v; for (int i=1; i<=n; ++i) { if (s[i]=='{') v.push_back(i); else if (s[i]=='}') nxt[v.back()] = i, v.pop_back(); } build(2,n-1,{}); while (q--) { scanf("%s", t+1); int m = strlen(t+1), ok = 1, now = 0; for (int i=1; i<=m; ++i) if (t[i]=='"') { int j = i+1; while (t[j]!='"') ++j; auto u = string(t+i,t+j+1); if (!mp.count(u)) { ok = 0; break; } now = (now*(int64_t)B+ID(u))%P; i = j; } if (!ok||!ans.count(now)) puts("Error!"); else { int l, r; tie(l,r) = ans[now]; for (int i=l; i<=r; ++i) putchar(s[i]); puts(""); } } } }
J
题意:给定串,对于每种区间长度,求多少个区间满足前一半和后一半循环同构
暴力枚举区间,考虑如何检验两个串$A,B$循环同构,可以发现,要么$A=B$,要么是$B$的一个后缀等于$A$的前缀,然后其余部分相等,暴力kmp+hash可过,复杂度不知道咋证
正解是用manacher,考虑每个位置为中心的所有区间,依次把头尾插到一个新串中,那么原串循环同构就等价于新串中一个后缀是双回文串,可以用manacher检验
#include <bits/stdc++.h> using namespace std; const int N = 5e3+10; const int P1 = 876756319, B1 = 999991; const int P2 = 799898821, B2 = 233333; int a[N], f[N], fac[N], fac1[N], fac2[N], f1[N], f2[N]; vector<int> ans[N]; pair<int,int> Hash(int l, int r) { int x = (f1[r]-f1[l-1]*(int64_t)fac1[r-l+1])%P1; int y = (f2[r]-f2[l-1]*(int64_t)fac2[r-l+1])%P2; if (x<0) x += P1; if (y<0) y += P2; return pair<int,int>(x,y); } void getfail(int *s, int *f, int n) { f[0] = f[1] = 0; for (int i=1,j=0; i<n; ++i) { while (j&&s[i]!=s[j]) j=f[j]; if (s[i]==s[j]) ++j; f[i+1] = j; } } int main() { fac1[0] = fac2[0] = 1; for (int i=1; i<N; ++i) { fac1[i] = fac1[i-1]*(int64_t)B1%P1; fac2[i] = fac2[i-1]*(int64_t)B2%P2; } int T; scanf("%d", &T); while (T--) { int n, m; scanf("%d%d", &n, &m); for (int i=1; i<=n; ++i) ans[i].clear(); for (int i=1; i<=n; ++i) { scanf("%d", &a[i]); f1[i] = (f1[i-1]*(int64_t)B1+a[i])%P1; f2[i] = (f2[i-1]*(int64_t)B2+a[i])%P2; } for (int i=1; i<=n; ++i) { getfail(a+i,f,n-i+1); for (int j=i+1; j<=n; j+=2) { int len = (j-i+1)/2, mid = j-len; auto chk = [&]() { if (Hash(i,mid)==Hash(mid+1,j)) return true; int t = f[j-i+1]; while (t>=len) t = f[t]; while (t) { if (Hash(2*mid+1-j+t,mid)==Hash(mid+1,j-t)) return true; t = f[t]; } return false; }; if (chk()) ans[len*2].push_back(i); } } int cnt = 0, tot = 0; for (int i=1; i<=n; ++i) if (ans[i].size()) ++cnt, tot += ans[i].size(); printf("%d %d\n", tot, cnt); for (int i=1; i<=n; ++i) if (ans[i].size()) { printf("%d %d", i, (int)ans[i].size()); for (int t:ans[i]) printf(" %d", t); puts(""); } } }
K
题意:给定串,每次操作交换任意两字符或者填一个任意字符,求最少多少次操作能得到合法逆波兰表达式
$*$看做$-1$,数字看做$1$,那么合法逆波兰等价于每个前缀和为正,$n$很小直接暴力枚举多少个$*$移到前面即可
CF587D
题意:给定图,要求删除一些边,使得删的边没有公共顶点,并且剩余边中不存在颜色相同且有公共顶点的边,并且删的边边权最大值最小
考虑二分答案,第一个限制就是要求一个点的邻接边至多删一条,第二个限制就是每个点同色邻接边至多留一条不删,这是一个2sat关系,前缀优化建图即可
#include <bits/stdc++.h> using namespace std; const int N = 5e4+10; int n, m; struct _ { int to,c,t,id; bool operator < (const _ &rhs) const { return c<rhs.c; } }; vector<_> g[N]; struct twosat { int tot,n,dfn[N*10],low[N*10],sccno[N*10],clk,scnt; int s[N*10],s_top; vector<int> g[N*10]; void dfs(int x) { dfn[s[++s_top]=x]=low[x]=++clk; for (int y:g[x]) { if (!dfn[y]) dfs(y),low[x]=min(low[x],low[y]); else if (!sccno[y]) low[x]=min(low[x],dfn[y]); } if (low[x]==dfn[x]) for (int u=++scnt; sccno[u=s[s_top--]]=scnt,u!=x;) ; } void init(int _n) { for (int i=1; i<=tot; ++i) { g[i].clear(); dfn[i] = sccno[i] = 0; } tot = 2*_n, n = _n; n = _n, clk = scnt = 0; } bool solve() { for (int i=1; i<=tot; ++i) if (!dfn[i]) dfs(i); for (int i=1; i<=n; ++i) if (sccno[i]==sccno[i+n]) return false; return true; } } sat; bool chk(int lim) { sat.init(m); for (int i=1; i<=n; ++i) { for (int j=0; j<g[i].size(); ++j) { int id = ++sat.tot; sat.g[id].push_back(g[i][j].id+m); if (j) { sat.g[id].push_back(id-1); if (g[i][j].t<=lim) sat.g[g[i][j].id].push_back(id-1); } } for (int j=(int)g[i].size()-1; j>=0; --j) { int id = ++sat.tot; sat.g[id].push_back(g[i][j].id+m); if (j+1!=g[i].size()) { sat.g[id].push_back(id-1); if (g[i][j].t<=lim) sat.g[g[i][j].id].push_back(id-1); } } for (int j=0; j<g[i].size(); ++j) { if (g[i][j].t>lim) sat.g[g[i][j].id].push_back(g[i][j].id+m); } for (int j=0,k; j<g[i].size(); j=k) { k = j; while (k<g[i].size()&&g[i][k].c==g[i][j].c) ++k; for (int u=j; u<k; ++u) { int id = ++sat.tot; sat.g[id].push_back(g[i][u].id); if (u!=j) { sat.g[id].push_back(id-1); sat.g[g[i][u].id+m].push_back(id-1); } } for (int u=k-1; u>=j; --u) { int id = ++sat.tot; sat.g[id].push_back(g[i][u].id); if (u+1!=k) { sat.g[id].push_back(id-1); sat.g[g[i][u].id+m].push_back(id-1); } } } } return sat.solve(); } int main() { scanf("%d%d", &n, &m); for (int i=1; i<=m; ++i) { int u, v, c, t; scanf("%d%d%d%d", &u, &v, &c, &t); g[u].push_back({v,c,t,i}); g[v].push_back({u,c,t,i}); } for (int i=1; i<=n; ++i) sort(g[i].begin(),g[i].end()); int l=0,r=1e9+10,ans=-1; while (l<=r) { int mid = (l+r)/2; if (chk(mid)) ans=mid,r=mid-1; else l=mid+1; } if (ans==-1) puts("No"); else { puts("Yes"); chk(ans); int cnt = 0; for (int i=1; i<=m; ++i) if (sat.sccno[i]<sat.sccno[i+m]) ++cnt; printf("%d %d\n", ans, cnt); for (int i=1; i<=m; ++i) if (sat.sccno[i]<sat.sccno[i+m]) printf("%d ", i); puts(""); } }
CF1007D
题意:给定树上$m$个路径对,要求从每个对中选一个路径,使得$m$条路径不相交
CF935F
题意:定义$f(A)=\sum\limits_{1\le i<n} |a_i-a_{i+1}|$,两种操作$(1,l,r,x)$表示$[l,r]$内选一个位置+x,输出最大f(A),$(2,l,r,x)$表示区间加,操作1不改变序列
如果区间长度$>1$,可以发现+x后答案一定增大,讨论一下每个位置的贡献
若$a_{i}\ge a_{i-1}\wedge a_{i}\ge a_{i+1}$,那么贡献$2x$
若$a_{i}\ge a_{i-1}\wedge a_{i}\le a_{i+1}$,那么贡献$2(x-a_{i+1}+a_{i})$
若$a_{i}\le a_{i-1}\wedge a_{i}\ge a_{i+1}$,那么贡献$2(x-a_{i-1}+a_{i})$
若$a_{i}\le a_{i-1}\wedge a_{i}\le a_{i+1}$,那么贡献$2(x-a_{i-1}-a_{i+1}+2a_{i})$
可以发现答案均为$2x-max(0,a_{i+1}-a_{i})-max(0,a_{i-1}-a_{i})$
用线段树维护即可
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10; int n, a[N]; struct bit { int64_t c[N]; void add(int l, int r, int x) { for (; l<=n; l+=l&-l) c[l] += x; for (++r; r<=n; r+=r&-r) c[r] -= x; } int64_t query(int x) { int64_t ans = a[x]; for (; x; x^=x&-x) ans += c[x]; return ans; } } tr; struct seg { int64_t ma[N<<2]; void pu(int o) { ma[o] = max(ma[o*2], ma[o*2+1]); } void build(int o, int l, int r) { if (l==r) ma[o] = -max(0,a[l-1]-a[l])-max(0,a[l+1]-a[l]); else { int mid = (l+r)/2; build(o<<1,l,mid),build(o<<1|1,mid+1,r); pu(o); } } int64_t query(int o, int l, int r, int ql, int qr) { if (ql<=l&&r<=qr) return ma[o]; int mid = (l+r)/2; if (mid>=qr) return query(o<<1,l,mid,ql,qr); if (mid<ql) return query(o<<1|1,mid+1,r,ql,qr); return max(query(o<<1,l,mid,ql,qr),query(o<<1|1,mid+1,r,ql,qr)); } void update(int o, int l, int r, int x, int64_t v) { if (l==r) { ma[o] = v; return; } int mid = (l+r)/2; if (mid>=x) update(o<<1,l,mid,x,v); else update(o<<1|1,mid+1,r,x,v); pu(o); } } T; int main() { scanf("%d", &n); int64_t ans = 0; for (int i=1; i<=n; ++i) scanf("%d", &a[i]); for (int i=1; i<n; ++i) ans += abs(a[i]-a[i+1]); auto gao = [&](int x) { if (x==0||x==n) return 0ll; return abs(tr.query(x)-tr.query(x+1)); }; auto calc = [&](int p, int x) { int64_t w = -gao(p-1)-gao(p); a[p] += x; w += gao(p-1)+gao(p); a[p] -= x; return w; }; if (n>2) T.build(1,2,n-1); int q; scanf("%d", &q); while (q--) { int op,l,r,x; scanf("%d%d%d%d", &op, &l, &r, &x); if (op==1) { int64_t ma = max(calc(l,x),calc(r,x)); if (r-l<=1) { printf("%lld\n", ans+ma); continue; } if (n>2) ma = max(ma, 2ll*x+2ll*T.query(1,2,n-1,l+1,r-1)); printf("%lld\n", ans+ma); } else { ans -= gao(l-1)+gao(r); tr.add(l,r,x); ans += gao(l-1)+gao(r); auto upd = [&](int p) { if (p<2||p>=n) return; T.update(1,2,n-1,p,-max(0ll,tr.query(p-1)-tr.query(p))-max(0ll,tr.query(p+1)-tr.query(p))); }; if (l!=1) upd(l-1),upd(l); if (r!=n) upd(r),upd(r+1); } } }
CF1109E
题意:给定序列,区间乘,单点除,区间求和取模
核心观察是$a \space mod\space m = \frac{a}{gcd(a,m)}\space mod \space \frac{m}{gcd(a,m)} \cdot gcd(a,m)$
把$m$分解一下,线段树维护三个值:每个数对于每个$m$的素因子的次幂,每个数除去$m$的素因子后模$m$的值,模$m$的区间和
这样复杂度就是$O(10 n\log n)$
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10; int n,m,q,phi,a[N]; vector<pair<int,int>> v; int qpow(int a, int n, int m) { int ans = 1; for (; n; n>>=1,a=a*(int64_t)a%m) if (n&1) ans=ans*(int64_t)a%m; return ans; } int qpow(int a, int n) { int ans = 1; for (int k=0; k<n; ++k) ans *= a; return ans; } struct bit { int b[N],c[N]; void add(int l, int r, int v) { for (int x=l; x<=n; x+=x&-x) c[x] += v; for (int x=r+1; x<=n; x+=x&-x) c[x] -= v; } int query(int x) { int ans = b[x]; for (; x; x^=x&-x) ans += c[x]; return ans; } } tr[10]; struct seg { int sum[N<<2],tag[N<<2]; void build(int o, int l, int r) { if (l==r) { sum[o] = a[l]%m; return; } tag[o] = 1; int mid = (l+r)/2; build(o<<1,l,mid), build(o<<1|1,mid+1,r); pu(o); } void pu(int o) { sum[o] = (sum[o<<1]+sum[o<<1|1])%m; } void pd(int o) { if (tag[o]!=1) { tag[o<<1] = tag[o<<1]*(int64_t)tag[o]%m; tag[o<<1|1] = tag[o<<1|1]*(int64_t)tag[o]%m; sum[o<<1] = sum[o<<1]*(int64_t)tag[o]%m; sum[o<<1|1] = sum[o<<1|1]*(int64_t)tag[o]%m; tag[o] = 1; } } void mul(int o, int l, int r, int ql, int qr, int x) { if (ql<=l&&r<=qr) { sum[o] = sum[o]*(int64_t)x%m; tag[o] = tag[o]*(int64_t)x%m; return; } pd(o); int mid = (l+r)/2; if (mid>=ql) mul(o<<1,l,mid,ql,qr,x); if (mid<qr) mul(o<<1|1,mid+1,r,ql,qr,x); pu(o); } void upd(int o, int l, int r, int x, int v) { if (l==r) { sum[o] = v; return; } pd(o); int mid = (l+r)/2; if (mid>=x) upd(o<<1,l,mid,x,v); else upd(o<<1|1,mid+1,r,x,v); pu(o); } int query(int o, int l, int r, int ql, int qr) { if (ql<=l&&r<=qr) return sum[o]; pd(o); int mid = (l+r)/2, ans = 0; if (mid>=ql) ans = query(o<<1,l,mid,ql,qr); if (mid<qr) ans = (ans+query(o<<1|1,mid+1,r,ql,qr))%m; return ans; } } T,Y; int main() { scanf("%d%d", &n, &m); int mm = m; phi = 1; for (int i=2; i*i<=mm; ++i) if (mm%i==0) { int cnt = 0; while (mm%i==0) mm /= i, ++cnt; v.push_back({i,cnt}); phi *= (i-1)*qpow(i,cnt-1); } if (mm>1) v.push_back({mm,1}), phi *= mm-1; for (int i=1; i<=n; ++i) scanf("%d", &a[i]); T.build(1,1,n); for (int i=1; i<=n; ++i) { for (int k=0; k<v.size(); ++k) { while (a[i]%v[k].first==0) a[i] /= v[k].first, ++tr[k].b[i]; } } Y.build(1,1,n); scanf("%d", &q); while (q--) { int op, l, r, x, p; scanf("%d", &op); if (op==1) { scanf("%d%d%d", &l, &r, &x); T.mul(1,1,n,l,r,x); for (int k=0; k<v.size(); ++k) { int cnt = 0; while (x%v[k].first==0) x /= v[k].first, ++cnt; if (cnt) tr[k].add(l,r,cnt); } Y.mul(1,1,n,l,r,x); } else if (op==2) { scanf("%d%d", &p, &x); int g = 1, tmp[10]; for (int k=0; k<v.size(); ++k) { int cnt = 0; while (x%v[k].first==0) x /= v[k].first, ++cnt; tr[k].b[p] -= cnt; tmp[k] = tr[k].query(p); g = g*qpow(v[k].first,min(tmp[k],v[k].second)); } int val = 1; for (int k=0; k<v.size(); ++k) { val = val*(int64_t)qpow(v[k].first,tmp[k]-min(tmp[k],v[k].second),m/g)%(m/g); } int t = Y.query(1,1,n,p,p)*(int64_t)qpow(x,phi-1,m)%m; Y.upd(1,1,n,p,t); val = val*(int64_t)g%m*t%m; T.upd(1,1,n,p,val); } else { scanf("%d%d", &l, &r); printf("%d\n", T.query(1,1,n,l,r)); } } }
CF1175F
题意:给定序列,求多少个区间$[l,r]$为1到r-l+1的排列
合法区间要恰好包含1个1,讨论最大值在1的左侧还是右侧,hash统计
#include <bits/stdc++.h> using namespace std; const int N = 3e5+10; mt19937_64 rd(time(0)); int n, ans, a[N]; uint64_t ID[N],w[N],s[N]; void solve() { for (int i=1,mx=0; i<=n; ++i) { s[i] = s[i-1]^ID[a[i]]; mx = max(mx, a[i]); if (a[i]==1) mx = 1; else if (i>=mx&&(s[i]^s[i-mx])==w[mx]) ++ans; } } int main() { scanf("%d", &n); for (int i=1; i<=n; ++i) { scanf("%d", &a[i]); ID[i] = rd(); w[i] = w[i-1]^ID[i]; if (a[i]==1) ++ans; } solve(); reverse(a+1,a+1+n); solve(); printf("%d\n", ans); }
icpc 2017 xi'an
A
题意:给定序列$A$和常数$k$,每次询问给出区间$[l,r]$,求$[l,r]$中选出一些数异或后与$k$按位或的最大值
$k$为$1$的为相当于一定为$1$,那么构建线性基时直接除去,对也就是说A[i]&~k建线性基即可
就转化为经典问题,给定序列,求区间异或最大值
直接线段树暴力维护线性基,复杂度是3个log,数据范围很小,可以过
一个稍微聪明点的办法是,贪心从高位到低位维护线性基每个数最后出现位置,询问只考虑处理到位置$r$时最后出现位置$\ge l$的基
时空复杂度都是$O(n\log A)$,如果把询问离线,空间复杂度也可以优化到$O(n)$
#include <bits/stdc++.h> using namespace std; const int N = 1e4+10; int val[N][27],pos[N][27]; int main() { int T; scanf("%d", &T); while (T--) { int n, q, k; scanf("%d%d%d", &n, &q, &k); for (int i=1; i<=n; ++i) { int t; scanf("%d", &t); t &= ~k; memcpy(val[i],val[i-1],sizeof val[i]); memcpy(pos[i],pos[i-1],sizeof pos[i]); int p = i; for (int j=26; j>=0; --j) if (t>>j&1) { if (pos[i][j]<p) { swap(val[i][j],t); swap(pos[i][j],p); } t ^= val[i][j]; if (!t) break; } } while (q--) { int l, r; scanf("%d%d", &l, &r); int ans = k; for (int j=26; j>=0; --j) if (pos[r][j]>=l) ans=max(ans,ans^val[r][j]); printf("%d\n", ans); } } }
B 排序双指针
C
D
E
题意:给定无向图和序列$A$,求添一些边,${dist}_i$为$i$到$1$最短距离,使得$\sum (A_i-{dist}_i)^2$最小
添边后dist序列每个值不变或减小,并且对于一条边$(u,v)$,要满足$|{dist}_u-{dist}_v|\le 1$,按照切糕构造最小割即可
#include <bits/stdc++.h> using namespace std; #include <bits/stdc++.h> using namespace std; const int N = 1e6+10, S = N-2, T = N-1, INF = 0x3f3f3f3f; int n, m, tot; struct edge { int to,w,next; edge(int to=0,int w=0,int next=0):to(to),w(w),next(next) {} } e[N]; int head[N], dep[N], cur[N], cnt=1; queue<int> Q; int bfs() { for (int i=1; i<=tot; ++i) dep[i]=INF,cur[i]=head[i]; dep[S]=INF,cur[S]=head[S]; dep[T]=INF,cur[T]=head[T]; dep[S]=0,Q.push(S); while (Q.size()) { int u = Q.front(); Q.pop(); for (int i=head[u]; i; i=e[i].next) { if (dep[e[i].to]>dep[u]+1&&e[i].w) { dep[e[i].to]=dep[u]+1; Q.push(e[i].to); } } } return dep[T]!=INF; } int dfs(int x, int w) { if (x==T) return w; int used = 0; for (int i=cur[x]; i; i=e[i].next) { cur[x] = i; if (dep[e[i].to]==dep[x]+1&&e[i].w) { int f = dfs(e[i].to,min(w-used,e[i].w)); if (f) used+=f,e[i].w-=f,e[i^1].w+=f; if (used==w) break; } } return used; } int dinic() { int ans = 0; while (bfs()) ans+=dfs(S,INF); return ans; } void add(int u, int v, int w) { e[++cnt] = edge(v,w,head[u]); head[u] = cnt; e[++cnt] = edge(u,0,head[v]); head[v] = cnt; } int g[50][50],dist[50],a[50],mp[50][50]; int main() { while (~scanf("%d%d", &n, &m)) { memset(g,0,sizeof g); for (int i=1; i<=m; ++i) { int u, v; scanf("%d%d", &u, &v); g[u][v] = g[v][u] = 1; } for (int i=1; i<=n; ++i) scanf("%d", &a[i]); tot = 0; for (int i=1; i<=n; ++i) for (int j=1; j<n; ++j) mp[i][j] = ++tot; cnt = 1; for (int i=1; i<=tot; ++i) head[i] = 0; head[S] = head[T] = 0; memset(dist,0x3f,sizeof dist); dist[1] = 0; queue<int> q; while (q.size()) { int u = q.front(); q.pop(); for (int v=1; v<=n; ++v) if (g[u][v]&&dist[v]==INF) { dist[v] = dist[u]+1; q.push(v); } } for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) if (g[i][j]) { for (int d=1;d+1<n;++d) { add(mp[i][d+1],mp[j][d],INF); } } for (int d=0; d<n; ++d) { int u = mp[i][d], v = mp[i][d+1], cap = (a[i]-d)*(a[i]-d); if (d==0) u = S; if (d+1==n) v = T; if (d>dist[i]) cap = INF; add(u,v,cap); } } printf("%d\n", dinic()); } }
F 答案是n/(n+m)
G
异或可以按位考虑,转化为01序列的情况,用线段树维护即可,复杂度是$O(q\log n\log A)$
实际上这道题有个$O((n+q)\log A)$的做法,考虑维护每次询问与上次之差,类似于莫队,暴力大概像这样写
int qry(int l, int r) { if (l>r) swap(l,r); return s[r]^s[l-1]; } int f(int k, int l, int r) { int ans = 0; for (int i=l; i<=r; ++i) ans += qry(k,i); return ans; } int main() { ql = 1, qr = 0; for (int i=1; i<=q; ++i) { scanf("%d%d", &l, &r); while (qr<r) ++qr,ans[i]+=f(qr,ql,qr); while (qr>r) ans[i]-=f(qr,ql,qr),--qr; while (ql>l) --ql,ans[i]+=f(ql,ql,qr); while (ql<l) ans[i]-=f(ql,ql,qr),++ql; } for (int i=1; i<=q; ++i) printf("%d\n", ans[i]+=ans[i-1]); }
然后可以发现$f$可以拆成前缀,离线扫描线即可,加了个快读只跑了91ms
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10, P = 1e9+7; int a[N],ans[N],cnt[N],pw[N][20],c[N][20]; int64_t h[N]; struct Q {int l,r,id;}; vector<Q> g[N]; char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf; #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) inline int rd() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) x=x*10+(ch^48),ch=getchar(); return x*f; } int main() { for (int i=1; i<N; ++i) { pw[i][0] = i; for (int j=1; j<20; ++j) pw[i][j] = pw[i][j-1]*2%P; } int T=rd(); while (T--) { int n=rd(), q=rd(); for (int i=1; i<=n; ++i) { h[i] = a[i] = rd(); a[i] ^= a[i-1]; g[i].clear(); } for (int i=0; i<20; ++i) { c[1][i] = a[1]>>i&1; cnt[i] = 0; } for (int i=2; i<=n; ++i) { for (int j=0; j<20; ++j) { if (a[i-2]>>j&1) ++cnt[j]; c[i][j] = c[i-1][j]+(a[i]>>j&1); } for (int j=0; j<20; ++j) { if (a[i]>>j&1) h[i] += pw[i-1ll-cnt[j]][j]; else h[i] += pw[cnt[j]][j]; } h[i] = (h[i]+h[i-1])%P; } int ql = 1, qr = 0; for (int i=1; i<=q; ++i) { int l = rd(), r = rd(); ans[i] = 0; if (qr<r) { if (ql>1) g[ql-1].push_back({qr+1,r,-i}); ans[i] = (ans[i]+h[r]-h[qr])%P; qr = r; } if (qr>r) { if (ql>1) g[ql-1].push_back({r+1,qr,i}); ans[i] = (ans[i]+h[r]-h[qr])%P; qr = r; } if (ql>l) { if (ql>1) g[ql-1].push_back({ql,qr,i}); if (l>1) g[l-1].push_back({l,qr,-i}); ans[i] = (ans[i]+h[ql-1]-h[l-1])%P; ql = l; } if (ql<l) { if (l>1) g[l-1].push_back({l,qr,-i}); if (ql>1) g[ql-1].push_back({ql,qr,i}); ans[i] = (ans[i]+h[ql-1]-h[l-1])%P; ql = l; } } for (int i=1; i<=n; ++i) { for (auto &&t:g[i]) { int w = 0; for (int d=0; d<20; ++d) { int64_t u = c[t.r][d]-c[t.l-1][d]; w = (w+pw[c[i-1][d]][d]*(t.r-t.l+1-u)+pw[i-c[i-1][d]][d]*u)%P; } if (t.id>0) ans[t.id] = (ans[t.id]+w)%P; else ans[-t.id] = (ans[-t.id]-w)%P; } } for (int i=1; i<=q; ++i) { ans[i] = (ans[i]+ans[i-1])%P; if (ans[i]<0) ans[i] += P; printf("%d\n", ans[i]); } } }
H 线段树贪心模拟
I 时限很长,直接冲莫队
#include <bits/stdc++.h> using namespace std; const int N = 1e6+100; int a[N],b[N],blo[N],ans[N][10],cur[10],vis[N],sz[N]; struct _ { int l,r,id; bool operator < (const _ &rhs) const { return blo[l]^blo[rhs.l]?l<rhs.l:blo[l]&1?r<rhs.r:r>rhs.r; } } e[N]; void add(int x) { ++vis[x]; if (vis[x]>1) return; int szl = 0, szr = 0; while (szl<=10&&x-szl>=2&&b[x-szl-1]+1==b[x-szl]&&vis[x-szl-1]) ++szl; while (szr<=10&&b[x+szr]+1==b[x+szr+1]&&vis[x+szr+1]) ++szr; if (1<=szl&&szl<=10) --cur[szl-1]; if (1<=szr&&szr<=10) --cur[szr-1]; if (1<=szl+szr+1&&szl+szr+1<=10) ++cur[szl+szr]; } void del(int x) { --vis[x]; if (vis[x]>=1) return; int szl = 0, szr = 0; while (szl<=10&&x-szl>=2&&b[x-szl-1]+1==b[x-szl]&&vis[x-szl-1]) ++szl; while (szr<=10&&b[x+szr]+1==b[x+szr+1]&&vis[x+szr+1]) ++szr; if (1<=szl&&szl<=10) ++cur[szl-1]; if (1<=szr&&szr<=10) ++cur[szr-1]; if (1<=szl+szr+1&&szl+szr+1<=10) --cur[szl+szr]; } int main() { int T; scanf("%d", &T); while (T--) { int n, m; scanf("%d%d", &n, &m); int sqn = pow(n,0.55); for (int i=1; i<=n; ++i) { scanf("%d", &a[i]); b[i] = a[i]; blo[i] = i/sqn; } sort(b+1,b+1+n); *b = unique(b+1,b+1+n)-b-1; for (int i=1; i<=*b; ++i) vis[i] = 0; for (int i=1; i<=n; ++i) a[i] = lower_bound(b+1,b+1+*b,a[i])-b; for (int i=1; i<=m; ++i) scanf("%d%d",&e[i].l,&e[i].r),e[i].id=i; sort(e+1,e+1+m); int ql = 1, qr = 0; memset(cur,0,sizeof cur); for (int i=1; i<=m; ++i) { while (ql>e[i].l) add(a[--ql]); while (qr<e[i].r) add(a[++qr]); while (ql<e[i].l) del(a[ql++]); while (qr>e[i].r) del(a[qr--]); for (int j=0; j<10; ++j) ans[e[i].id][j] = cur[j]%10; } for (int i=1; i<=m; ++i) { for (int j=0; j<10; ++j) putchar(ans[i][j]+'0'); putchar(10); } } }
J 跑下状压即可
K
B题的加强版,$a$变为$k-a$,然后从大到小排序,那么合法等价于至少有$1$个数$\ge a_1$,至少$2$个数$\ge a_2$,..... 双指针处理一下即可
#include <bits/stdc++.h> using namespace std; const int N = 2e5+10; int a[N], b[N], nxt[N]; struct seg { int mx[N<<2], tag[N<<2]; void pu(int o) { mx[o] = min(mx[o<<1], mx[o<<1|1]); } void add(int o, int64_t v) { mx[o] += v, tag[o] += v; } void build(int o, int l, int r) { tag[o] = 0; if (l==r) { mx[o] = -l; return; } int mid = (l+r)/2; build(o<<1,l,mid); build(o<<1|1,mid+1,r); pu(o); } void pd(int o) { if (tag[o]) { add(o<<1,tag[o]); add(o<<1|1,tag[o]); tag[o] = 0; } } void add(int o, int l, int r, int ql, int qr, int64_t v) { if (ql<=l&&r<=qr) return add(o,v); pd(o); int mid = (l+r)/2; if (mid>=ql) add(o<<1,l,mid,ql,qr,v); if (mid<qr) add(o<<1|1,mid+1,r,ql,qr,v); pu(o); } int query(int o, int l, int r, int ql, int qr) { if (ql<=l&&r<=qr) return mx[o]; pd(o); int mid = (l+r)/2, ans = 1e9; if (mid>=ql) ans = max(ans, query(o<<1,l,mid,ql,qr)); if (mid<qr) ans = max(ans, query(o<<1|1,mid+1,r,ql,qr)); return ans; } } tr; int main() { int T; scanf("%d", &T); while (T--) { int n, m, k; scanf("%d%d%d", &n, &m, &k); for (int i=1; i<=n; ++i) scanf("%d", &a[i]),a[i]=max(0,k-a[i]); for (int i=1; i<=m; ++i) scanf("%d", &b[i]), nxt[i] = 1e9; tr.build(1,1,n); sort(a+1,a+n+1,greater<int>()); auto add = [&](int x, int v) { if (x<a[n]) return; int p = lower_bound(a+1,a+1+n,x,greater<int>())-a; tr.add(1,1,n,p,n,v); }; int now = 1; for (int i=1; i<=m; ++i) { while (now<=m&&tr.mx[1]<0) add(b[now++],1); if (tr.mx[1]<0) break; nxt[i] = now-1; add(b[i],-1); } int q; scanf("%d", &q); while (q--) { int l, r; scanf("%d%d", &l, &r); puts(nxt[l]<=r?"1":"0"); } } }
2016 icpc shenyang
A 答案A+B+max(A,B)
B 答案H+12C+16O
C 矩阵快速幂
D
E 枚举每个点作为编号最小点时的团保证不重复,度数很小直接暴力枚举组合数即可
F
G
H
I 可以得到转移${dp}_x=min {dp}_f+({dep}_x-{dep}_f)^2+p$,${dp}_1=-p$,可以斜率优化一下
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10; const int64_t INF = 0x3f3f3f3f3f3f3f3f; vector<pair<int,int>> g[N]; int fa[N],dep[N],p; int64_t dp[N]; deque<int> q{1}; int64_t dy(int a, int b) { return dp[a]+dep[a]*(int64_t)dep[a]-dp[b]-dep[b]*(int64_t)dep[b]; } int64_t dx(int a, int b) { return 2*(dep[a]-dep[b]); } int chk(int a, int b, int c) { return dy(a,b)*dx(b,c)<=dy(b,c)*dx(a,b); } void dfs(int x, int f, int d) { fa[x] = f, dep[x] = d; vector<int> L,R; if (x!=1) { while (q.size()>1&&dy(q[1],q[0])<=dx(q[1],q[0])*d) { L.push_back(q[0]); q.pop_front(); } dp[x] = dp[q[0]]+(dep[x]-dep[q[0]])*(dep[x]-dep[q[0]])+p; while (q.size()>1&&chk(x,q.back(),q[q.size()-2])) { R.push_back(q[0]); q.pop_back(); } q.push_back(x); } for (auto &e:g[x]) if (e.first!=f) dfs(e.first,x,d+e.second); if (x!=1) { q.pop_back(); for (int i=(int)R.size()-1; i>=0; --i) q.push_back(R[i]); for (int i=(int)L.size()-1; i>=0; --i) q.push_front(L[i]); L.clear(),R.clear(); } } int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d%d", &n, &p); for (int i=1; i<=n; ++i) { g[i].clear(); dp[i] = INF; } dp[1] = -p; for (int i=1; i<n; ++i) { int u, v, w; scanf("%d%d%d", &u, &v, &w); g[u].push_back({v,w}); g[v].push_back({u,w}); } dfs(1,0,0); int64_t ma = 0; for (int i=2; i<=n; ++i) ma = max(ma, dp[i]); printf("%lld\n", ma); } }
J
题意:给定$n$点$n$边无向连通图,每次操作把点$u$距离$k$范围的点权$+d$,询问点$u$距离$k$范围内的点权和,$k\le 2$
就是个死做题,首先需要知道$n$点$n$边的无向连通图也就是一棵基环树,先考虑树上怎么做
对距离$\le k$的点进行更新,那么显然不能直接暴力遍历,有个经典套路就是可以在父亲上打标记
比方说要对点$x$的所有二级儿子权值$+d$,那么就在$x$上打标记,查询一个点权值的时候加上二级父亲的标记值即可
那么对于这个题,需要对$\le 2$范围的点求和,把这个范围拆成$3$种情况
$a_x$表示点$x$所有二级儿子权值和,$b_x$表示$x$所有儿子权值和,$c_x$表示$x$权值和
那么$x$范围$2$内的权值和就为$a_x+b_x+b_{f_x}+c_{f_x}+c_{f_{f_x}}$
对于更新操作,再维护一个${cnt}_{x,1/2}$表示$x$的儿子或二级儿子个数,通过打标记技巧,就可以$O(1)$维护$a,b,c$了
那么对于基环树的情况,可以先dfs求出一棵生成树,提出那条环边,更新时特判一下经过环边的贡献即可
这题数据好像比较弱,之前判错了一个地方结果竟然过了
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10; int n, q, dep[N], fa[N], A, B, cnt[N][2]; int64_t c[N][3], b[N][2], a[N], d, ans; vector<int> g[N]; void dfs(int x, int f, int d) { fa[x] = f, dep[x] = d; ++cnt[f][0], ++cnt[fa[f]][1]; for (int y:g[x]) if (y!=f) { if (!dep[y]) dfs(y,x,d+1); else if (dep[y]<dep[x]) A = x, B = y; } } void upd(int x, int k) { if (!x) return; int f = fa[x], ff = fa[f]; c[x][k] += d; if (k==0) { b[f][0] += d; a[ff] += d; } else if (k==1) { b[x][0] += d*cnt[x][0]; a[f] += d*cnt[x][0]; } else { b[x][1] += d; a[x] += d*cnt[x][1]; } } void qry(int x, int k) { if (!x) return; int f = fa[x], ff = fa[f]; if (k==0) ans += c[x][0]+c[f][1]+c[ff][2]; if (k==1) ans += b[x][0]+b[f][1]*cnt[x][0]; if (k==2) ans += a[x]; } void solve(void(*gao)(int,int),int x, int k) { int f = fa[x], ff = fa[f]; if (k==0) return gao(x,0); if (x==A) gao(B,0); if (k==1) { gao(x,1),gao(x,0),gao(f,0); if (x==B) gao(A,0); return; } gao(x,2),gao(x,1),gao(f,1); if (!f) gao(x,0); if (x==A) gao(B,1),gao(fa[B],0); if (x!=A||dep[A]-dep[B]>=3) gao(f,0); if (x!=A||dep[A]-dep[B]>3) gao(ff,0); if (fa[x]==A||x==fa[A]&&B!=f&&B!=ff) gao(B,0); if (x==fa[B]||fa[x]==B&&fa[A]!=x&&fa[fa[A]]!=x) gao(A,0); if (x==B) { if (dep[A]-dep[B]>2) gao(A,0); if (dep[A]-dep[B]>3) gao(fa[A],0); gao(A,1); } } int main() { int T; scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i=1; i<=n; ++i) { g[i].clear(); fa[i] = dep[i] = cnt[i][0] = cnt[i][1] = 0; c[i][0] = c[i][1] = c[i][2] = b[i][0] = b[i][1] = a[i] = 0; } for (int i=0; i<n; ++i) { int u, v; scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } dfs(1,0,1); scanf("%d", &q); while (q--) { char s[20]; int u, k; scanf("%s%d%d", s, &u, &k); ans = 0; if (s[0]=='M') scanf("%lld", &d), solve(upd,u,k); else solve(qry,u,k), printf("%lld\n", ans); } } }
K
L
M