「2020-2021 集训队作业」大鱼治水 题解

题目大意

现在有一棵树,每条边可以为虚或为实,但是在任何时刻任何一个点的所有向下的边中最多只能有一条实边。初始虚实关系由你决定。

现在有 ​ q q q 个要求,要你在 35​ 步操作内令 ​ u u u 到根的路径变为全是实边。(达成目标后,你仍可继续操作,只要保证结束时仍满足要求即可)

各要求之间不独立。(你上一个要求的结束状态会沿袭到下一个要求的起始状态。)

SOLUTION

重链剖分

首先我们将重边设为实边

三种转移(选择最优(也就是最大步数最小)的一种)

  • 最先设定重儿子

    • 若当前是重儿子,需要0步

    • 若当前是轻儿子,需要4步:将重儿子取消,改为ta,将ta取消,改回重儿子

  • 全部不设定重儿子,需要2步:将重儿子设ta,将ta取消

  • 最先不设定重儿子

    • 若当前重儿子,需要1步:将重儿子设为ta

    • 若当前轻儿子,需要3步:将重儿子取消,将重儿子设ta,将ta取消

我们使用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示到第 i i i个点使用第 j j j种转移的最大步数

然后在 i n i t init init时确定使用哪一种方式

并做好(设/不设 重儿子 的)处理

CODE

#include<bits/stdc++.h>
#include"river.h"
const int N=1e5+2;
int fa[N],son[N],kd[N],vis[N],top[N],dp[N][4],sz[N],to[N],nxt[N],hd[N],cnt;
void add(int x,int y){
	to[++cnt]=y;
	nxt[cnt]=hd[x];
	hd[x]=cnt;
}
void dfs(int x){
	sz[x]=1; 
	son[x]=-1;
	for(int i=hd[x];i;i=nxt[i]){
		int y=to[i];
		fa[y]=x;
		dfs(y);
		sz[x]+=sz[y];
		if(sz[y]>sz[son[x]]||son[x]==-1)son[x]=y;
	}
	int mx=0;
	for(int i=hd[x];i;i=nxt[i]){
		int y=to[i];
		if(y==son[x]) continue; 
		mx=std::max(mx,dp[y][kd[y]]);
	}
	dp[x][1]=std::max(mx+4,dp[son[x]][kd[son[x]]]);//重儿子 
	dp[x][2]=std::max(mx+2,dp[son[x]][kd[son[x]]]+2);
	dp[x][3]=std::max(mx+3,dp[son[x]][kd[son[x]]]+1);
	int mn=std::min({dp[x][1],dp[x][2],dp[x][3]});
	if(mn==dp[x][1])kd[x]=1;
	else if(mn==dp[x][2])kd[x]=2;
	else if(mn==dp[x][3])kd[x]=3;
}
void dfs(int x,int t){//重链剖分 
	top[x]=t;
	if(son[x]==-1)return;
	if(kd[x]==1)dfs(son[x],t);
	else dfs(son[x],son[x]);
	for(int i=hd[x];i;i=nxt[i]){
		int y=to[i];
		if(y==son[x]) continue; 
		dfs(y,y);
	}
}
std::vector<int> init(int n, std::vector<int> father){
	for(int i=0;i<=n-2;i++){
		add(father[i],i+2);
	}//知道了爸爸,只见一条边即可 
	dfs(1);
	dfs(1,1);
	std::vector<int> H(n,0);
	for(int i=1;i<=n;i++){
		if(kd[i]==1){
			H[i-1]=son[i];//指定重儿子 
		}
	} 
	return H;
}
void prepare(int x){
	int now=x;
	while(fa[now]){//为下次做准备 
		if(kd[fa[now]]==1){//保持正常重儿子 
			if(now!=son[fa[now]]){//如果不是重儿子的话 
				set(fa[now],0);
				set(fa[now],son[fa[now]]);
			}
		}else if(kd[fa[now]]==2){//保证一直没有重儿子 
			set(fa[now],0);
		}else{
			if(vis[fa[now]]){
				if(now!=son[fa[now]]){
					set(fa[now],0);
					vis[fa[now]]=0;
				}
			}else{
				if(now==son[fa[now]])vis[fa[now]]=1;
				else set(fa[now],0);
			} 
		}
		now=top[fa[now]];
	}
}
void solve(int x){
	int now=x;
	while(fa[now]){
		if(kd[fa[now]]==1){
			if(now!=son[fa[now]]){
				set(fa[now],0);
				set(fa[now],now);
			}
		}else if(kd[fa[now]]==2){
			set(fa[now],now);
		}else{
			if(vis[fa[now]]){
				if(now!=son[fa[now]]){
					set(fa[now],0);
					set(fa[now],now);
				}
			}else{
				set(fa[now],now);
			}
		}
		now=top[fa[now]];
	}
	wait();
	prepare(x);//为下一次做准备 
}

完结撒花❀

★,°:.☆( ̄▽ ̄)/$:.°★

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值