八一特别行动
护身旁,战远方,有我啊,一生骄傲为我站立的地方,我的样子就是中国的模样。
A. 南
解释一下g[i]的转移:+f[i]其实就是+f[i]*1把后面计算过的每个贡献都+1,就像这样
1 每次只计算了后面的,从后往前转移,新加的是1,原来的就让出位置,而再加一个1是当前多
12 加的步数。
123
(⊙o⊙)…总之就是……(⊙o⊙)…(⊙o⊙)…感性理解……
https://blog.csdn.net/u011815404/article/details/88973189
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e6 + 2; const ll mod = 1e9 + 7; const int INF = 2147483647; const int lim = 1e4 + 1; double n, f[maxn], g[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { scanf("%lf", &n); for(int i=n-1; i>=0; i--) { f[i] = f[i+1] + n/(n-i)*1.0; } for(int i=n-1; i>=0; i--) { g[i] = i/(n-i)*1.0*f[i]+g[i+1]+f[i+1]+n/(n-i)*1.0; } printf("%.2lf\n", g[0]); return 0; }
B. 昌
暴力就是,枚举每一种叶子の可能的排列,直接填进去向上合并。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 3e5 + 2; const ll mod = 1e9 + 7; const int INF = 2147483647; const int lim = 1e4 + 1; vector<int> son[maxn], leaf; int b[maxn], w[maxn], ans, sz, n; bool tag[maxn], v[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int get_ans(int u) { if(!son[u].size()) { return w[u]; } int res = 0; if(!tag[u]) res = INF; for(int i=0; i<son[u].size(); i++) { int v = son[u][i]; if(tag[u]) res = max(res, get_ans(v)); else res = min(res, get_ans(v)); } return res; } void check() { for(int i=0; i<sz; i++) { int u = leaf[i]; w[u] = b[i+1]; } ans = max(ans, get_ans(1)); } void dfs(int a, int n) { if(a > n) { check(); return; } for(int i=1; i<=n; i++) { if(v[i]) continue; v[i] = 1; b[a] = i; dfs(a+1, n); v[i] = 0; } } int main() { n = read(); for(int i=1; i<=n; i++) { tag[i] = read(); } for(int i=2; i<=n; i++) { int x = read(); son[x].push_back(i); } for(int i=2; i<=n; i++) { if(!son[i].size()) { leaf.push_back(i); } } sz = leaf.size(); dfs(1, sz); printf("%d\n", ans); return 0; }
假设最后根节点的权值>=x,那么min操作的话,儿子的权值必须都>=x,max操作的话,儿子的权值必须有一个>=x,假设要满足根节点的权值>=x需要有cnt(也就是下文的f[1])个叶子>=x,无论x是多少cnt唯一确定,x可以是小于(设f[i]是以i为根的子树内至少有多少个叶子需要大于某一个值)f[i]的集合里的任意一个数,它最大时有x+f[1]-1<=k,也就是x<=k-f[1]+1,这就可以通过求出f[1]来得到x。其实不止有cnt,f数组的所有值都不会随x变化而变化,所以x是不是相同不重要,它一定可以向上合并。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 3e5 + 2; const ll mod = 1e9 + 7; const int INF = 2147483647; const int lim = 1e4 + 1; vector<int> son[maxn], leaf; int f[maxn], sz, n; bool tag[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int dfs(int u) { if(!son[u].size()) { return 1; } if(tag[u]) f[u] = INF; for(int i=0; i<son[u].size(); i++) { int v = son[u][i]; if(tag[u]) { f[u] = min(f[u], dfs(v)); } else f[u] += dfs(v); } return f[u]; } int main() { n = read(); for(int i=1; i<=n; i++) { tag[i] = read(); } for(int i=2; i<=n; i++) { int x = read(); son[x].push_back(i); } for(int i=2; i<=n; i++) { if(!son[i].size()) { leaf.push_back(i); } } sz = leaf.size(); printf("%d\n", sz-dfs(1)+1); return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
const int N=1000000+100;ll mod=998244353; int n,op[300000+10],fa[300000+10],dp[300000+10]; bool isn[300000+10],ok[300000+10]; int main() { //freopen("shuju.in","r",stdin); // freopen("dfs.txt","w",stdout); n=re();int ki=0; _f(i,1,n)op[i]=re(); _f(i,2,n)fa[i]=re(),isn[fa[i]]=1;//不是儿子 _f(i,1,n)if(!isn[i])dp[i]=1,++ki; f_(i,n,1) { if(op[fa[i]]) { if(ok[fa[i]])dp[fa[i]]=min(dp[fa[i]],dp[i]); else dp[fa[i]]=dp[i],ok[fa[i]]=1; } else { dp[fa[i]]+=dp[i]; } } //_f(i,1,n)chu("dp[%d]:%d\n",i,dp[i]); chu("%d",ki-dp[1]+1); return 0; } /* dp[rt]=cnt:rt节点要>=某个值,至少的叶子节点需要>=这个值的数量 如果rt取max,那么只要有一个就行 dp[rt]=min(dp[son]) 如果rt取min,必须所有叶子节点都要>=这个值 dp[rt]=sigma(dp[son])
C. 起
赛时思路小心被误导:不管合法的布料有多大,拼接的部分都相同是那四个字母,所以我预处理出了每个B做右下角,W做左下角……的最大正方形边长,最后询问时先找到拼接部分,把边界和四个正方形最大边长去个min,就是1/4正方形的边长,emmm答案就有了,过样例过得挺顺,交上去就爆0,不过预处理正方形什么的好像和正解有相似之处,大概是我写丑了……
还有一个傻乎乎的操作是n=1直接输出一个0结束程序……n=0不代表q=0呀……这就是送分不要。
正解告诉我们,原来正方形的大小可以通过递推得到,而不一定要用二维树状数组求和+判断……
V是符号反了,应该开口朝下……前缀和什么只是个说法,其实说成后缀和似乎更形象……最重要的是注意细节,check函数里要减去的只要有一点点边界超出了范围都不行的所有情况,val*2是长度,u-val*2+1是边界,因为长度恰好为val*2的属于合法情况,所以u-val*2+1是合法的边界,把合法区间的左右边界求和要减掉不合法的,也就是u-val*2+1+1,就是我一直以为我多加的那条宽为1的线它属于合法的范围……emmm想这个想了好久(注释掉的是没用前缀和+二分优化的TLE40代码and调试)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 502; const ll mod = 1e9 + 7; const int INF = 2147483647; const int lim = 1e4 + 1; int n, m, q, g[maxn][maxn][5], ans; int f[maxn][maxn][maxn]; char s[maxn][maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } /* int check(int a, int b, int u, int r) { int ans = 0; for(int i=a; i<=u; i++) { for(int j=b; j<=r; j++) { for(int k=1; (i+2*k-1<=u && j+2*k-1<=r); k++) { if(f[i][j][k]) { ans = max(ans, k); } } } } return (ans+ans)*(ans+ans); } */ bool check(int val, int a, int b, int u, int w) { //printf("f[2][1][1] = %d\n", f[2][1][1]); //printf("f[2][3][1] = %d\n", f[2][3][1]); //printf("a = %d w+1 = %d\n", a, w+1); //printf("%d %d %d %d\n", f[a][b][val], f[u+1][b][val], f[a][w+1][val], f[u+1][w+1][val]); if(!val) return 1; if(f[a][b][val]-f[u+1-val*2+1][b][val]-f[a][w+1-val*2+1][val]+f[u+1-val*2+1][w+1-val*2+1][val]) return 1; return 0; } int main() { n = read(); m = read(); q = read(); for(int i=1; i<=n; i++) { scanf("%s", s[i]+1); } for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(s[i][j] == 'B') { g[i][j][1] = min(g[i-1][j][1], min(g[i][j-1][1], g[i-1][j-1][1]))+1; } else if(s[i][j] == 'W') { g[i][j][2] = min(g[i-1][j][2], min(g[i][j-1][2], g[i-1][j-1][2]))+1; } else if(s[i][j] == 'P') { g[i][j][3] = min(g[i-1][j][3], min(g[i][j-1][3], g[i-1][j-1][3]))+1; } else { g[i][j][4] = min(g[i-1][j][4], min(g[i][j-1][4], g[i-1][j-1][4]))+1; } } } for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { for(int k=1; (i+2*k-1<=n&&j+2*k-1<=m); k++) { if(g[i+k-1][j+k-1][1]>=k && g[i+k-1][j+2*k-1][2]==k && g[i+2*k-1][j+k-1][3]==k && g[i+2*k-1][j+2*k-1][4]==k) { f[i][j][k] = 1; } } } } //printf("f[2][1][1] = %d\n", f[2][1][1]); for(int i=n; i>=1; i--) { for(int j=m; j>=1; j--) { for(int k=1; (i+2*k-1<=n&&j+2*k-1<=m); k++) { f[i][j][k] += f[i][j+1][k]+f[i+1][j][k]-f[i+1][j+1][k]; } } } //printf("cmp main: %d %d\n", f[2][1][1], f[2][3][1]); while(q--) { int a = read(), b = read(), u = read(), w = read(); int l = 0, r = min(u-a+1, w-b+1)/2; //printf("r = %d\n", r); while(l < r) { int mid = (l + r + 1) >> 1; //printf("mid = %d\n", mid); if(check(mid, a, b, u, w)) l = mid; else r = mid-1; } ans = (l+l)*(l+l); printf("%d\n", ans); } return 0; }
D. 义
由于sqrt(n)之后的物品是不可能被取完的,所以前后可以分开dp,f[i][j](前)和g[i][j](后)中i的含义不一样,f[i][j]中的i指的是取完第i种物品,g[i][j]中的i指的是拿了i件sqrt(n)+1~n中的物品,j都是指已经被占用的空间。所以转移方程就好理解了,g的第二条方程记得i+1。
由于定义的差别,也就可以理解为什么记录答案时,f一直用n,因为他必须讨论结束时的状态,而f的前一项却需要循环,因为从sqrt(n)+1~n中取几个都可以,数组中的没一个数都代表了最后的时间(f中被滚动数组压掉的部分是没到最后时间的过程)。
再解释一下代码中的g[1][0]=1,根据定义这显然是不合法的,所以它不能出现在dp转移之前,可是后来为什么会需要这种不合法的东西,是因为用前sqrt(n)种物品把背包填满也是可行解,但是把它和本来没有值的g[...][0]相乘就变成了0,那为什么不把g[...][0]全附上1,因为f[n][n]作为可行解搭配sqrt(n)之后的不选只能算是一种情况,多加就成了重复。
所以说g[1][0]=1只是为了记录答案的方便并没有实际意义,不过或许在最开始把f[n][n]直接加到ans其实更方便还省了一层循环……
重复一遍,最后一行的g[i][j+sqrt(n)+1]=...的转移应该写成g[i+1][j+sqrt(n)+1]=...
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 2; const ll mod = 23333333; const int INF = 2147483647; const int lim = 1e4 + 1; int n, m, f[2][maxn], g[320][maxn], ans; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { n = read(); m = sqrt(n); f[0][0] = f[1][0] = 1; int p = 0; for(int i=1; i<=m; i++) { p ^= 1; for(int j=1; j<=n; j++) { f[p][j] = f[p^1][j]; if(j >= i) f[p][j] = (f[p][j]+f[p][j-i])%mod; if(j >= i*(i+1)) { f[p][j] = (f[p][j]-f[p^1][j-i*(i+1)]+mod)%mod; } } } g[0][0] = 1; for(int i=0; i<=m; i++) { for(int j=0; j<=n; j++) { if(j+i<=n && i) g[i][j+i] = (g[i][j+i]+g[i][j])%mod; if(j+m+1<=n) g[i+1][j+m+1] = (g[i+1][j+m+1]+g[i][j])%mod; } } g[1][0] = 1; for(int i=0; i<=n; i++) { for(int j=1; j<=m; j++) { ans = (ans+1ll*f[p][i]*g[j][n-i]%mod)%mod; } } printf("%d\n", ans); return 0; }