暑假集训CSP提高模拟4
暑假集训CSP提高模拟4
组题人: @Delov
P134. White and Black
-
翻转方式:从根节点进行
,若遇到黑点就进行翻转。最后一定能使全树均为白点,即不存在无解的情况。进而有每个点仅会被主动翻转一次,且翻转顺序与最终结果无关。 -
观察到
,考虑枚举黑点。 -
若点
与父亲节点颜色不同,则会贡献一次翻转;否则则桶记录下每个节点子节点中有多少个黑点,最后减去即可。点击查看代码
struct node { int nxt,to; }e[400010]; int head[400010],fa[400010],son[400010],dep[400010],vis[400010],sum[400010],s[400010],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int father) { fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { son[x]++; dfs(e[i].to,x); } } bool cmp(int a,int b) { return dep[a]>dep[b]; } int main() { int n,q,u,v,m,ans,i,j; cin>>n>>q; for(i=2;i<=n;i++) { cin>>u; v=i; add(u,v); } dfs(1,0); for(i=1;i<=q;i++) { cin>>m; ans=0; for(j=1;j<=m;j++) { cin>>s[j]; vis[s[j]]=1; } sort(s+1,s+1+m,cmp);//从深到浅处理,好像不排序也行 for(j=1;j<=m;j++) { if(vis[fa[s[j]]]==1) { sum[fa[s[j]]]++;//统计黑点个数 } else { ans++; } } for(j=1;j<=m;j++) { ans+=son[s[j]]-sum[s[j]];//统计因翻转变成黑点的白点 vis[s[j]]=sum[s[j]]=0; } cout<<ans<<endl; } return 0; }
P137. White and White
-
令
,设 表示把前 个数分成 段时的最小价值总和,状态转移方程为 ,边界为 。- 直接暴力转移的话时间复杂度为
,更改枚举顺序加滚动数组优化后仅能通过 。
- 直接暴力转移的话时间复杂度为
-
题面隐含着一个
。进而有对于 的两个决策 一定有 ,进而有 -
若
,又因为 ,有 。所以取使 最小的 进行转移即可。- 反证法即可证明。
-
最终有
即为所求。点击查看代码
ll a[500010],sum[500010],f[500010][2]; int main() { ll n,k,p,minn,pos,i,j; scanf("%lld%lld%lld",&n,&k,&p); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); sum[i]=sum[i-1]+a[i]; } f[0][0]=0; for(j=1;j<=k;j++) { minn=f[j-1][(j-1)&1]; pos=j-1; for(i=j;i<=n;i++) { f[i][j&1]=f[pos][(j-1)&1]+(sum[i]-sum[pos])%p; if(f[i][(j-1)&1]<minn) { minn=f[i][(j-1)&1]; pos=i; } } } printf("%lld\n",f[n][k&1]); return 0; }
P132. Black and Black
-
因要满足
,考虑尽量减小 着填。 -
先将
填入 ,记 , 然后考虑调整。 -
当
时, 若 存在一个前缀和为正数或存在一个后缀和为负数则一定有解。- 存在一个前缀和为正数则一定存在一个前缀和等于
。接着将这个前缀减去 就会满足题意。 - 存在一个后缀和为负数则一定存在一个后缀和等于
。接着将这个后缀加上 就会满足题意。
- 存在一个前缀和为正数则一定存在一个前缀和等于
-
当
时,直接输出 。 -
当
时,同理。点击查看代码
ll a[200010],b[200010],pre[200010],suf[200010]; int main() { ll n,sum=0,flag=0,pos=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=i; sum+=a[i]*b[i]; pre[i]=pre[i-1]+a[i]; } for(i=n;i>=1;i--) { suf[i]=suf[i+1]+a[i]; } if(sum==0) { cout<<"Yes"<<endl; for(i=1;i<=n;i++) { cout<<b[i]<<" "; } } else { if(sum>0) { for(i=1;i<=n;i++) { flag|=(pre[i]>=1||suf[i]<=-1); } if(flag==0) { cout<<"No"<<endl; } else { for(i=1;i<=n;i++) { if(pre[i]==1) { pos=i; break; } } if(pos==0) { for(i=n;i>=1;i--) { if(suf[i]==-1) { pos=i; break; } } for(i=pos;i<=n;i++) { b[i]+=sum; } } else { for(i=1;i<=pos;i++) { b[i]-=sum; } } cout<<"Yes"<<endl; for(i=1;i<=n;i++) { cout<<b[i]<<" "; } } } else { for(i=1;i<=n;i++) { flag|=(pre[i]<=-1||suf[i]>=1); } if(flag==0) { cout<<"No"<<endl; } else { for(i=1;i<=n;i++) { if(pre[i]==-1) { pos=i; break; } } if(pos==0) { for(i=n;i>=1;i--) { if(suf[i]==1) { pos=i; break; } } for(i=pos;i<=n;i++) { b[i]-=sum; } } else { for(i=1;i<=pos;i++) { b[i]+=sum; } } cout<<"Yes"<<endl; for(i=1;i<=n;i++) { cout<<b[i]<<" "; } } } } return 0; }
P136. Black and White
-
原题: luogu P2056 [ZJOI2007] 捉迷藏 | luogu P4115 Qtree4 | SP2666 QTREE4 - Query on a tree IV
-
考虑对链进行分治,用线段树维护重链。
-
假设我们当前查询到一条链,则树上路径只会分为经过这条链和不经过这条链两种情况。
-
后者递归处理,问题主要在前者。
-
对于
维护从 到子树内黑点的最长距离 ,从 到子树内黑点的最长距离 ,经过 的所有路径的最长长度 。pushup
过程与维护区间最大子段和基本一致。 -
但边界
比较难处理。若 是黑点,因为可以自己到自己和存在边权,所以最后结果要与 取 ;若 是白点则直接继承。- 设
表示从 到子树内黑点的最长距离, 表示从 到子树内黑点的次长距离。 - 重链上的节点可以线段树继承;故可以只考虑黑点在轻链上的情况,从
走到黑点一定要经过 的轻儿子的节点,特殊处理。
- 设
-
修改时链内部线段树维护,链外部暴力跳重链直到根节点,注意删去原影响。
-
可删堆可以写平板电视或
multiset
代替。点击查看代码
struct SDBH { multiset<int,greater<int> >s; void insert(int x) { s.insert(x); } void del(int x) { multiset<int,greater<int> >::iterator it=s.find(x); if(it!=s.end()) { s.erase(it); } } int top() { return (s.empty()==0)?*s.begin():-0x3f3f3f3f; } }q[200010],ans; struct node { int nxt,to,w; }e[200010]; int head[200010],col[200010],siz[200010],fa[200010],dep[200010],son[200010],top[200010],dfn[200010],pos[200010],len[200010],cnt=0,tot=0; void add(int u,int v,int w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs1(int x,int father,int w) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+w; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x,e[i].w); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int father,int id) { top[x]=id; len[id]++; tot++; dfn[x]=tot; pos[tot]=x;//记录长度 if(son[x]!=0) { dfs2(son[x],x,id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father&&e[i].to!=son[x]) { dfs2(e[i].to,x,e[i].to); } } } } int dis(int u,int v) { return dep[u]-dep[v]; } struct SMT { int root[1000010],rt_sum=0; struct SegmentTree { int ls,rs,lmax,rmax,ans; }tree[2000010]; #define lson(rt) tree[rt].ls #define rson(rt) tree[rt].rs void pushup(int rt,int l,int r) { int mid=(l+r)/2; tree[rt].lmax=max(tree[lson(rt)].lmax,dis(pos[mid+1],pos[l])+tree[rson(rt)].lmax); tree[rt].rmax=max(tree[rson(rt)].rmax,dis(pos[r],pos[mid])+tree[lson(rt)].rmax); tree[rt].ans=max(tree[lson(rt)].ans,max(tree[rson(rt)].ans,tree[lson(rt)].rmax+dis(pos[mid+1],pos[mid])+tree[rson(rt)].lmax)); } int build_rt() { rt_sum++; return rt_sum; } void build_tree(int &rt,int l,int r) { rt=build_rt(); if(l==r) { int x=pos[l],len1,len2; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { q[x].insert(tree[root[e[i].to]].lmax+e[i].w); } } len1=q[x].top();//最长 q[x].del(len1); len2=q[x].top();//次长 q[x].insert(len1); tree[rt].lmax=tree[rt].rmax=max(len1,0); tree[rt].ans=max(0,max(len1,len1+len2)); return; } int mid=(l+r)/2; build_tree(lson(rt),l,mid); build_tree(rson(rt),mid+1,r); pushup(rt,l,r); } void update(int rt,int l,int r,int x,int id) { if(l==r) { if(x!=id) { q[x].insert(tree[root[id]].lmax+dis(id,x)); } int len1=q[x].top(); q[x].del(len1); int len2=q[x].top(); q[x].insert(len1); if(col[x]==0) { tree[rt].lmax=tree[rt].rmax=max(len1,0); tree[rt].ans=max(0,max(len1,len1+len2)); } else { tree[rt].lmax=tree[rt].rmax=len1;//直接继承 tree[rt].ans=len1+len2; } return; } int mid=(l+r)/2; if(dfn[x]<=mid) { update(lson(rt),l,mid,x,id); } else { update(rson(rt),mid+1,r,x,id); } pushup(rt,l,r); } }T; void update(int x) { for(int last=x;x!=0;last=top[x],x=fa[top[x]]) { int len1=T.tree[T.root[top[x]]].ans; if(fa[top[x]]!=0) { q[fa[top[x]]].del(T.tree[T.root[top[x]]].lmax+dis(top[x],fa[top[x]]));//删除原影响 } T.update(T.root[top[x]],dfn[top[x]],dfn[top[x]]+len[top[x]]-1,x,last); int len2=T.tree[T.root[top[x]]].ans; if(len1!=len2)//两次值不一样则在答案集合重删去原影响 { ans.del(len1); ans.insert(len2); } } } int main() { int n,u,v,w,m,sum=0,i; char pd; scanf("%d",&n); for(i=1;i<=n-1;i++) { scanf("%d%d",&u,&v); w=1; add(u,v,w); add(v,u,w); } dfs1(1,0,1); dfs2(1,0,1); for(i=n;i>=1;i--)//dfs 序降序建树 { u=pos[i]; if(u==top[u]) { T.build_tree(T.root[u],dfn[u],dfn[u]+len[u]-1); ans.insert(T.tree[T.root[u]].ans); } } scanf("%d",&m); sum=n; for(i=1;i<=m;i++) { scanf("%s",&pd); if(pd=='C') { scanf("%d",&u); col[u]^=1; sum+=((col[u]==0)?1:-1); update(u); } else { printf("%d\n",(sum==0)?-1:ans.top()); } } return 0; }
-
没找到线段树维护直径的做法,但官方题解给的是这个做法,挂一下。
总结
以为翻转顺序会影响最终答案,所以要用 。想到正解后又被自己 了。还是受题目背景影响较大的问题。- 三个
数据范围有较大差别,以为会像 LibreOJ 6560. 小奇取石子 一样面向数据点分治。 - 空间又开大了,加上没有进行滚动数组优化,挂了
。
- 三个
赛时以为是类似 初三奥赛模拟测试1 T3 混乱邪恶 一样的高级构造,遂没写。
后记
赛时进行改数据(不捆绑到捆绑)和时限( 到 ),卡掉了树状数组和一些常数大点的正解做法。- 改数据是因为随机数据下
即为答案,赛时 @oceans_of_stars 对于大数据点是这么做的, @Delov 联合群内众人没能卡掉,被迫捆绑后放 , @oceans_of_stars 赛后喜获一瓶芬达。 - 改时限是因为 @Delov 看 @wang54321 代码无意义取模太多,常数太大,感觉不顺眼,遂开小时限卡掉了
的树状数组写法。
- 改数据是因为随机数据下
直接下发了可执行文件 ,在 下可以正常用,但 下少权限,需要 【数据删除】 来获得权限。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18314678,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】