补题 发表于 2026-04-09 | 更新于 2026-04-09
| 阅读量:
2025CCPC:四川&2022河南 Problem I. 本质不同后缀 输出n个字符串的后缀串集大小.第一次尝试直接用set存储后缀串,果然还是过不了啊,直接MIE. 在紫皮书上看到过这样的处理方式:通过滚动哈希优化,将字符串转换为哈希值存储.
操作:对每个字符串,逆序遍历计算后缀哈希值
const ull base=31 ;set<ull>note; void solve () { int n;cin>>n; REP1 (i,1 ,n) { string s;cin>>s; ull cur=base; REP2 (i,s.size ()-1 ,0 ) { cur=base*(cur+s[i]); note.insert (cur); } } cout<<note.size ()<<'\n' ; }
Problem H. 胡图图 这题应该算签到题了…结果没考虑到原点附近的轴上点的特殊处理. 先画图模拟一下就没这么多事了
void solve () { ll x=0 ,y=0 ,X,Y; cin>>x>>y>>X>>Y; X=abs (X-x);Y=abs (Y-y); ll sp1=(X+1 )/2 ,sp2=(Y+1 )/2 ; if (X==0 &&Y==0 ){cout<<"0\n" ;return ;} if ((X==0 &&Y<=2 )||(Y==0 &&X<=2 ))cout<<2 <<'\n' ; else cout<<max (sp1,sp2)<<'\n' ; }
Problem F. 逆序对 wa四发,差点触发战败cg. 对长度为n的01序列,将序列中未确定字符替换为0或1来最大化逆序对.一开始想的很麻烦,什么前半补1后半补0,贪心,动态规划…就是没去试着暴力+优化.
最后想到之前写的一道很像的题https://codeforces.com/contest/2194/problem/B,只有第一次会遍历计算,后面单点修改再次计算的复杂度为O(1 ).
最后通过的思路:先计算全部替换为0时的情况,之后从前往后遍历,每次遇到替换位置时,改为1(填充01比例固定时,前1后0总是最优的)并维护变化(d=-pre1+suf0).
int a[N],b[N],suf[N];void solve () { int n;cin>>n; suf[n+1 ]=0 ; REP1 (i,1 ,n) { suf[i]=0 ; char x;cin>>x; if (x=='1' )a[i]=1 ; else if (x=='0' )a[i]=0 ; else {a[i]=-1 ;} } REP1 (i,1 ,n){ if (a[i]==-1 )b[i]=0 ; else b[i]=a[i]; } REP2 (i,n,1 )suf[i]=(b[i]==0 ?suf[i+1 ]+1 :suf[i+1 ]); ll res=0 ; REP1 (i,1 ,n){ if (b[i]==1 ){ res+=suf[i]; } } ll pre1=0 ,d=0 ,note=0 ; REP1 (i,1 ,n){ if (a[i]==1 ){ pre1++; } else if (a[i]==-1 ){ d+=-pre1+suf[i+1 ]; note=max (note,d); pre1++; } } cout<<res+note<<'\n' ; }
Problem J. Mex Tree 对k:0-n,找到点集满足mex=k的最大子树的点的数量
参考:https://zhuanlan.zhihu.com/p/570781133
差不多忘了怎么建树了,dfs也陌生到感觉有点神奇…
Problem E. Serval 的俳句 尝试分割成三段恰好满足条件的区间.每段的处理方式相同,只是目标长度和起点的差异,封装成函数方便书写.
int n;char s[N];int pt (int & p,int len) { unordered_map<int ,int >cc; while (p<=n) { int c=s[p]; cc[c]++; if (cc[c]>=len) { p++; return c; } p++; } return 0 ; } void solve () { cin>>n; REP1 (i,1 ,n)cin>>s[i]; int st=1 ; int c1=pt (st,5 ); int c2=pt (st,7 ); int c3=pt (st,5 ); if (c1&&c2&&c3) { REP1 (i,1 ,5 )cout<<char (c1); REP1 (i,1 ,7 )cout<<char (c2); REP1 (i,1 ,5 )cout<<char (c3); }else cout<<"none\n" ; }
Problem H. 旋转水管 dfs,最后记得重置vis.
void dfs (int x, int y, int in) { if (f||x < 0 || x>1 || y<1 || y>n)return ; if (vis[x][y])return ; vis[x][y]=1 ; if (x == 1 && y == ed) { if (((in == 3 || in == 4 ) && a[1 ][ed] == 'L' ) ||((in == 2 ) && a[1 ][ed] == 'I' )) { f = true ; return ; } } if (a[x][y] == 'L' ) { if (in == 1 ||in==2 ) { dfs (x, y - 1 , 3 ); dfs (x, y + 1 , 4 ); } if (in == 3 || in == 4 ) { dfs (x - 1 , y, 1 ); dfs (x + 1 , y, 2 ); } } else { if (in == 1 )dfs (x - 1 , y, 1 ); else if (in == 2 )dfs (x + 1 , y, 2 ); else if (in == 3 )dfs (x, y - 1 , 3 ); else dfs (x, y + 1 , 4 ); } vis[x][y]=0 ; }
Problem J. Mex Tree k==0,打印与权值为0的点相接的最大子树的点数就行
k!=0,目标集必须包含所有[0,k-1]范围内的数.
证伪比证真容易,比起判断所有连接枝中是否有完整包含[0,k-1]集的, 将权0点作为树根,k作为子树根节点,判断k子树是否都大于k更易实现.
对整棵树从0根节点dfs,尝试更新res[]. 对点u,遍历树枝,统计u树点数sz[u]并记录树枝最小权mi[u], mi[u]>u,说明主树满足条件,大小为n-sz[u]. 最后更新mi[u]=u
int n;int v[N],mi[N],sz[N],res[N];vector<int > g[N]; void dfs (int u,int fa) { sz[u]=1 ; mi[u]=inf; for (auto v:g[u]) { if (v==fa)continue ; dfs (v,u); sz[u]+=sz[v]; mi[u]=min (mi[u],mi[v]); } if (mi[u]>u)res[u]=n-sz[u]; mi[u]=min (mi[u],u); } void solve () { cin>>n; REP1 (i,1 ,n)cin>>v[i]; REP1 (i,2 ,n) { int x;cin>>x; g[v[i]].push_back (v[x]); g[v[x]].push_back (v[i]); } dfs (0 ,0 ); for (auto v:g[0 ])res[0 ]=max (res[0 ],sz[v]); REP1 (i,0 ,n-1 )cout<<res[i]<<' ' ; cout<<n<<'\n' ; }
一些单题 自底而上的聚合标记 有时候,把树当作特殊的图处理很方便.
https://codeforces.com/problemset/problem/1843/D
从所有叶子节点开始bfs向根节点汇总更新每个点下落到边界的情况数.
先将叶子节点更新标记并加入多源bfs的队列.
V way (n+1 ,0 ) ,vis (n+1 ,0 ) ,rem (n+1 ,0 ) ; queue<int >q; REP1 (i,1 ,n)rem[i]=g[i].size (); REP1 (i,2 ,n){ if (rem[i]==1 ){ way[i]=1 ;vis[i]=1 ; q.push (i); } }
反向dfs(关键),实时更新点的标记状态vis:
while (q.size ()){ int u=q.front ();q.pop (); for (int v:g[u]){ if (vis[v])continue ; way[v]+=way[u]; rem[v]--; if (rem[v]==1 &&v!=1 ){ q.push (v); vis[v]=1 ; } } }
对根单独处理:
if (!vis[1 ]){ for (int v:g[1 ])way[1 ]+=way[v]; }
示例代码则是直接用了dfs子树dp .真奇怪,如果形成一条链的话,不会栈溢出吗?因为担心这点,没有考虑用递归解决.
int dfs (int u,int f) { int sum=0 ; if (g[u].size ()==1 &&u!=1 ){ return way[u]=1 ; } for (auto v:g[u]){ if (v==f)continue ; sum+=dfs (v,u); } return way[u]=sum; }
但毕竟递归是非常自然的想法,尝试其他的等效方法:
等价way1 BFS建序 + 逆序DP
根节点一次bfs建立树序
V ord,pa (n+1 ); queue<int >q; q.push (1 );pa[1 ]=-1 ; while (!q.empty ()){ int u=q.front ();q.pop (); ord.push_back (u); for (auto v:g[u]){ if (v==pa[u])continue ; pa[v]=u;q.push (v); } }
从叶节点往上更新
REP2 (i,ord.size ()-1 ,0 ){ int u=ord[i]; if (g[u].size ()==1 &&u!=1 )way[u]=1 ; for (int v:g[u]){ if (pa[v]==u)way[u]+=way[v]; } }