暑假集训CSP提高模拟4

暑假集训CSP提高模拟4

组题人: @Delov

T1 P134. White and Black 0pts

  • 原题: [ARC148C] Lights Out on Tree

  • 翻转方式:从根节点进行 DFS ,若遇到黑点就进行翻转。最后一定能使全树均为白点,即不存在无解的情况。进而有每个点仅会被主动翻转一次,且翻转顺序与最终结果无关。

  • 观察到 i=1qmi2×105 ,考虑枚举黑点。

  • 若点 x 与父亲节点颜色不同,则会贡献一次翻转;否则则桶记录下每个节点子节点中有多少个黑点,最后减去即可。

    点击查看代码
    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;
    }
    

T2 P137. White and White 0pts

  • 原题: CF958C3 Encryption (hard)

  • sumi=j=1iaj ,设 fi,j 表示把前 i 个数分成 j 段时的最小价值总和,状态转移方程为 fi,j=minh=j1i1{fh,j1+(sumisumh)modp} ,边界为 f0,0=0

    • 直接暴力转移的话时间复杂度为 O(n2k) ,更改枚举顺序加滚动数组优化后仅能通过 Subtask1
  • 题面隐含着一个 fi,jsumi(modp) 。进而有对于 fi,j 的两个决策 x,y(xy) 一定有 fx,j1+sumisumxfy,j1+sumisumysumi(modp) ,进而有 fx,j1+(sumisumx)modpfy,j1+(sumisumy)modp(modp)

  • fx,j1<fy,j1 ,又因为 {(sumisumx)modp[0,p)(sumisumy)modp[0,p) ,有 fx,j1+(sumisumx)modpfy,j1+(sumisumy)modp 。所以取使 fh,j1 最小的 h 进行转移即可。

    • 反证法即可证明。
  • 最终有 fn,k 即为所求。

    点击查看代码
    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;
    }
    

T3 P132. Black and Black 0pts

  • 原题: [ARC153C] ± Increasing Sequence

  • 因要满足 |bi|2×1012 ,考虑尽量减小 |bi| 着填。

  • 先将 1n 填入 b ,记 s=i=1naibi , 然后考虑调整。

  • s>0 时, 若 {a} 存在一个前缀和为正数或存在一个后缀和为负数则一定有解。

    • 存在一个前缀和为正数则一定存在一个前缀和等于 1 。接着将这个前缀减去 s 就会满足题意。
    • 存在一个后缀和为负数则一定存在一个后缀和等于 1 。接着将这个后缀加上 s 就会满足题意。
  • s=0 时,直接输出 {b}

  • s<0 时,同理。

    点击查看代码
    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;
    }
    

T4 P136. Black and White 0pts

  • 原题: luogu P2056 [ZJOI2007] 捉迷藏 | luogu P4115 Qtree4 | SP2666 QTREE4 - Query on a tree IV

  • 考虑对链进行分治,用线段树维护重链。

  • 假设我们当前查询到一条链,则树上路径只会分为经过这条链和不经过这条链两种情况。

  • 后者递归处理,问题主要在前者。

  • 对于 [l,r] 维护从 l 到子树内黑点的最长距离 lmax ,从 r 到子树内黑点的最长距离 rmax ,经过 [l,r] 的所有路径的最长长度 anspushup 过程与维护区间最大子段和基本一致。

  • 但边界 [l,l] 比较难处理。若 l 是黑点,因为可以自己到自己和存在边权,所以最后结果要与 0max ;若 l 是白点则直接继承。

    • len1x 表示从 x 到子树内黑点的最长距离, len2x 表示从 x 到子树内黑点的次长距离。
    • 重链上的节点可以线段树继承;故可以只考虑黑点在轻链上的情况,从 x 走到黑点一定要经过 x 的轻儿子的节点,特殊处理。
  • 修改时链内部线段树维护,链外部暴力跳重链直到根节点,注意删去原影响。

  • 可删堆可以写平板电视或 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;
    }
    
  • 没找到线段树维护直径的做法,但官方题解给的是这个做法,挂一下。

总结

  • T1 以为翻转顺序会影响最终答案,所以要用 DP 。想到正解后又被自己 Pass 了。还是受题目背景影响较大的问题。
  • T2
    • 三个 Subtask 数据范围有较大差别,以为会像 LibreOJ 6560. 小奇取石子 一样面向数据点分治。
    • 空间又开大了,加上没有进行滚动数组优化,挂了 10pts
  • T3 赛时以为是类似 初三奥赛模拟测试1 T3 混乱邪恶 一样的高级构造,遂没写。

后记

  • T2 赛时进行改数据(不捆绑到捆绑)和时限( 1.5s0.5s ),卡掉了树状数组和一些常数大点的正解做法。
    • 改数据是因为随机数据下 i=1naimodp 即为答案,赛时 @oceans_of_stars 对于大数据点是这么做的, @Delov 联合群内众人没能卡掉,被迫捆绑后放 hack@oceans_of_stars 赛后喜获一瓶芬达。
    • 改时限是因为 @Delov@wang54321 代码无意义取模太多,常数太大,感觉不顺眼,遂开小时限卡掉了 O(nklogp) 的树状数组写法。
  • T3 直接下发了可执行文件 checker ,在 Windows 下可以正常用,但 Linux 下少权限,需要 【数据删除】 来获得权限。
posted @   hzoi_Shadow  阅读(65)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
扩大
缩小
点击右上角即可分享
微信分享提示