“基环树”的简单应用——MAFIJA和WYF互相追逐的头题解

至于WYF大概是谁,不必多说,结合时事,人渣必须从严处理,从重判刑;代码量比较大,各位耐心看一下

WYF互相追逐的头

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题目的阅读量比较大,而且数据比较清奇,但是我们可以知道的是每一个头都在尽力追逐自己喜欢的那个头,因此和它同方向(也就是不知道往哪移动)必然就是早晚的事情。

所以如果互相追逐的头之间的追逐关系形成一个环的话,WYF的这些头到最后必然会不知道往哪去,因此只要我们能够判环,则此题可以解之。

但是我们在解题的时候要注意一点:输入的数据描述种存在一种可能性,使得两群头之间不存在任何追逐的关系,针对这种情况,解决方案就是将联通的节点之间打上标记,然后没有被标记的节点再次进行判环,其实只要找到了一个环就立刻输出即可,这道题特判,判分很松。

下放代码,代码量两题都很大,但是MAFIJA的部分代码可以完全抄WYF的头的。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct edge{
	int to,nxt;
};
int hlis[300000];
int hhp;
edge eds[600000];
int bef[300000];
int hd[300000];
int tl[300000];
int hp;
int chase,head;
bool vish[200001];
bool vis[200001];
int hsec;
void bud(int fr,int to){
	if(hd[fr]==-1){
		hd[fr]=hp;
		tl[fr]=hp;
		eds[hp].to=to;
		eds[hp].nxt=-1;
		hp++;
	}else{
		eds[tl[fr]].nxt=hp;
		tl[fr]=hp;
		eds[hp].to=to;
		eds[hp].nxt=-1;
		hp++;
	}
}
void df(int now,int up){
	if(!vis[now]){
	vis[now]=true;
	for(int a = hd[now];a!=-1;a=eds[a].nxt){
		if(eds[a].to!=up)
		df(eds[a].to,now);
	}
	}
}
int ph(int now,int up){
	if(!vish[now]){
		bef[now]=up;
		vish[now]=true;
		int rt;
		for(int a = hd[now];a!=-1;a=eds[a].nxt){
			if(eds[a].to!=up)
			rt=ph(eds[a].to,now);
			if(rt!=-1){
				return rt;
			}
		}
		return -1;
	}
	else{
		hsec = now;
		return up;
	}
}
void u(int now,int tar){
	if(now==tar){
		hlis[hhp]=now;
		hhp++;
		return;
	}else{
		hlis[hhp]=now;
		hhp++;
		u(bef[now],tar);
	}
}
int chasing[200001];
int main(){
	scanf("%d",&head);
	memset(hd,-1,sizeof hd);
	memset(tl,-1,sizeof tl);
	memset(chasing,0,sizeof chasing);
	for(int a = 1;a<=head;a++){
		scanf("%d",&chase);
		if(chase<a&&chasing[chase]==a){
			;
		}
		else{
			bud(a,chase);
			bud(chase,a);
			chasing[a]=chase;
		}
	}
	int huana;
	for(int a = 1;a<=head;a++){
		if(!vis[a]){
			memset(vis,false,sizeof vis);
			df(a,-1);
			memset(vish,false,sizeof vish);
			huana=ph(a,-1);
			if(huana!=-1){
				hhp = 0;
				u(huana,hsec);
				cout << hhp<<endl;
				for(int x = 0;x<hhp;x++){
					cout <<hlis[x]<<" "; 
				}
				break;
			}
		}
	}
	int x,y;
	for(int a = 0;a<head;a++){
		scanf("%d%d",&x,&y);
	}
	return 0;
} 

MAFIJA

在这里插入图片描述

简单聊一下题意和本质。狼人和民众数量未知,每个人之间互相揭发,狼人知道谁是什么角色,因此不会揭发同伙,平民则是在随机揭发,他们的揭发没有任何价值。

由此可见我们可以忽略平民的揭发,狼人揭发的一定是平民,但是我们现在并不知道狼人和平民谁是谁,这就是问题。

但是根据题意,我们可以发现:揭发狼人的和狼人所指的都是平民,这不就有点像无向图的关系嘛~每人揭发一人,构成基环树,可以找出来环然后每个环内的节点作为根节点,找出来根节点作狼人和作平民时的最大狼人数量。这里其实就是一个树形的动态规划(虽然做题的时候我并没有意识到,但我写出来了),先将子树根节点作狼人/平民时候的狼人最大数量算出来,然后在上一个根节点得出同类型的两个值,叶子节点上作为狼人时狼人数量是1,不做狼人时狼人数量是0,上一级节点如果作为平民,则下级节点既可以是狼人也可以是平民,因此我们取两个值中的最大值,如果作为狼人,则下一个节点只能取平民,然后狼人数量再+=1(因为上级节点作为狼人的数量应该计入)以此类推,直到环上的节点的最大狼人数量都被算出来,这道题就做出来了一大半。

然后我们手里就拿到了一个大概率不算很长,数据已经算好的环。这个时候我们根据狼人和狼人不能相邻的特性,随机切断这个环,再将任意一个端点作为树的根节点,而这个节点会被确定为平民,这样环中的上一个节点的种类就不再受到限制了。继续转移方程,最后在根节点中作为人民,得出一个ans_a。

那么如果真正的最优方案中根节点应该做狼人应该怎么办呢?很简单,因为如果这个点是狼人那么旁边的点一定是平民,因此在将环中的根节点错位一下,将旁边得点当成平民重复上一段的运算一遍,取两个方案中答案的最大值加在ans里,在程序考虑所有点后输出。

最后在提醒一点:本题和上一道题一样,存在两个子图不连通的情况,针对这种情况需要染色+循环找到未被设计的点,因此不少程序段会重复进行,数组数据需要注意清空
放上代码,这道题的代码我保守估计和上一个代码的查重率会在50%以上,剩下的代码写出来不多,不过挺费脑力,少一个环节都会爆蛋;

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct edge{
	int to,nxt;
};
int asrm[500001];
int asbt[500001];
int hlis[500001];
int hhp;
edge eds[1000000];
int bef[500001];
int hd[500001];
int tl[500001];
int hp;
int chase,head;
bool vish[500001];
bool vis[500001];
int hsec;
void bud(int fr,int to){
	if(hd[fr]==-1){
		hd[fr]=hp;
		tl[fr]=hp;
		eds[hp].to=to;
		eds[hp].nxt=-1;
		hp++;
	}else{
		eds[tl[fr]].nxt=hp;
		tl[fr]=hp;
		eds[hp].to=to;
		eds[hp].nxt=-1;
		hp++;
	}
}
void df(int now,int up){
	if(!vis[now]){
	vis[now]=true;
	for(int a = hd[now];a!=-1;a=eds[a].nxt){
		if(eds[a].to!=up)
		df(eds[a].to,now);
	}
	}
}
int ph(int now,int up){
	if(!vish[now]){
		bef[now]=up;
		vish[now]=true;
		int rt;
		for(int a = hd[now];a!=-1;a=eds[a].nxt){
			if(eds[a].to!=up)
			rt=ph(eds[a].to,now);
			if(rt!=-1){
				return rt;
			}
		}
		return -1;
	}
	else{
		hsec = now;
		return up;
	}
}
void u(int now,int tar){
	if(now==tar){
		vish[now]=true;
		hlis[hhp]=now;
		hhp++;
		return;
	}else{
		vish[now]=true;
		hlis[hhp]=now;
		hhp++;
		u(bef[now],tar);
	}
}
int chasing[500001];
int ans = 0;
int dphrm[500001];
int dphbt[500001];
void dfdp(int now,int up){
	for(int a = hd[now];a!=-1;a=eds[a].nxt){
		if(eds[a].to!=up&&!vish[eds[a].to]){
			dfdp(eds[a].to,now);
			asrm[now]+=max(asrm[eds[a].to],asbt[eds[a].to]);
			asbt[now]+=asrm[eds[a].to];
		}
	}
	asbt[now]+=1;
	return;
}
int main(){
	scanf("%d",&head);
	memset(asrm,0,sizeof asrm);
	memset(asbt,0,sizeof asbt);
	memset(hd,-1,sizeof hd);
	memset(tl,-1,sizeof tl);
	memset(chasing,0,sizeof chasing);
	for(int a = 1;a<=head;a++){
		scanf("%d",&chase);
		if(chase<a&&chasing[chase]==a){
			;
		}
		else{
			bud(a,chase);
			bud(chase,a);
			chasing[a]=chase;
		}
	}
	int huana;
	for(int a = 1;a<=head;a++){
		if(!vis[a]){
			df(a,-1);
			memset(vish,false,sizeof vish);
			huana=ph(a,-1);
			if(huana!=-1){//有环,错开DP 
				memset(vish,0,sizeof vish);
				hhp = 0;
				u(huana,hsec);
				for(int s = 0;s<hhp;s++){
					dfdp(hlis[s],-1);
					dphrm[s]=asrm[hlis[s]];
					dphbt[s]=asbt[hlis[s]];
				}
				for(int s = hhp-2;s>=0;s--){
					dphrm[s]+=max(dphrm[s+1],dphbt[s+1]);
					dphbt[s]+=dphrm[s+1];
				}
				int ansa = dphrm[0];
				for(int s = 0;s<hhp;s++){
					dphrm[s]=asrm[hlis[s]];
					dphbt[s]=asbt[hlis[s]];
				}
				for(int s = hhp;s>0;s--){
					dphrm[s]=dphrm[s-1];
					dphbt[s]=dphbt[s-1];
				}
				dphrm[0]=dphrm[hhp];
				dphbt[0]=dphbt[hhp];
				dphrm[hhp]=0;
				dphbt[hhp]=0;
				for(int s = hhp-2;s>=0;s--){
					dphrm[s]+=max(dphrm[s+1],dphbt[s+1]);
					dphbt[s]+=dphrm[s+1];
				}
				int ansb = dphrm[0];
				ans+=max(ansa,ansb);
			}else{//没环,直接DP 
				memset(vish,0,sizeof vish);
				dfdp(a,-1);
				ans+=max(asbt[a],asrm[a]);
			}
		}
	}
	cout << ans;
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值