luogu1330 封锁阳光大学

题目

题目描述
曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。

阳光大学的校园是一张由N个点构成的无向图,N个点之间由M条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在与这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。

询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。

输入格式
第一行:两个整数N,M

接下来M行:每行两个整数A,B,表示点A到点B之间有道路相连。

输出格式
仅一行:如果河蟹无法封锁所有道路,则输出“Impossible”,否则输出一个整数,表示最少需要多少只河蟹。

输入输出样例
输入 #1 复制
3 3
1 2
1 3
2 3
输出 #1 复制
Impossible
输入 #2 复制
3 2
1 2
2 3
输出 #2 复制
1
说明/提示
【数据规模】

1<=N<=10000,1<=M<=100000,任意两点之间最多有一条道路。

分析

考虑到题中要覆盖到所有的边,则深搜+染色不失为一种好方法

认认真真(欢欢喜喜)的写了一个深搜准备A了这道题,结果悲剧发生了——

这图不一定是联通图QAQ。

于是我光荣地炸掉了,不过感谢上苍我还有50分……

其实解决方法很简单,深搜模块可以不变(但要加上联通块的染色),
在主模块中套一个循环枚举边,加一个染色数组标记联通块,已染过
的联通块便不必再深搜

接下来就是主角——深搜

我的深搜只搜要放河蟹的点

放了河蟹的点标记为 1,
而与它相连的不能放河蟹的点标记为 2 ,

若出现矛盾,则此方案不行,可以返回
一个极大值,再从可行方案中选出河蟹最少的一种

注意到每一个边都要被覆盖,且只能在一个端点放河蟹,那么只要确定了一个点是否放河蟹,那么覆盖整个联通块的方案也就随之确定,

所以每一个联通块放河蟹的方法只有两种

即i点放河蟹和i点不放河蟹(其中i点是联通块中的任意一点)

又因为不能在两个端点同时放河蟹,所以联通块的方案又可以是v放河蟹与u放河蟹(u,v是联通块中任意一边的起点与终点);

因为我的深搜是只搜放河蟹的点,所以在主模块中只用dfs(u),dfs(v),再把两种方案所需的河蟹数相比较 ,选出最小值加入ans中,就像这样:

代码

		
		cnt=0; mem(vis1);//记得初始化 
        //cnt记录该方案的河蟹数
		dfs(u);//假设起点放河蟹 
		int x=cnt;
		
		cnt=0;mem(vis1);
		dfs(v);//假设终点放河蟹 
		int y=cnt;
		
        
		if(x==1000000&&y==1000000)
		{//如果两种方法都不能覆盖所有的边,直接输出不可行 
			printf("Impossible");
			return;
		}
		ans+=Min(x,y);//选择两个方案中最小的 

在深搜模块中,加一个双重循环,

第一层枚举与u(也就是放了河蟹的点)相连的边的另一端,标记为2,若其中有已经被标记为1的,则矛盾,返回极大值

第二层枚举与不能放河蟹的v点相连的点(u除外),这些点必须放河蟹,(因为v不放河蟹),进行
下一轮深搜 若已被标记为1 则跳过,若被标记为2,则与它必须放河蟹矛盾,返回极大值

for(int i=first[u];i;i=edge[i].nt)
//枚举与u相连的不能放河蟹的点 
	{
		int v=edge[i].v;
		if(vis1[v]==1) {cnt=1000000;return 0;}
        //如果终点也放了河蟹,则这种方案是不可行的 
		vis1[v]=2;
        //标记终点未放河蟹但不能再放河蟹 
		for(int j=first[v];j;j=edge[j].nt)
        //枚举下一层要放河蟹的点 
		{
			int v1=edge[j].v;
            //因为v不能放河蟹,则v1必须放河蟹 
			if(vis1[v1]==1) continue;
            //已经有一个端点有了河蟹 
			if(vis1[v1]==2) {cnt=1000000;return 0;}
            //两个都不能再放河蟹,则这条边不能被覆盖,该方案不可行 
			if(!dfs(v1)) return 0;//若该方案不行 
		}
	}

还有细节详见代码注释~

/*
User:Mandy.H.Y
language:c++
Problem:1330
深搜,广搜,并查集都行 
*/
#include<bits/stdc++.h>
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
#define mem(A) memset((A),0,sizeof(A))

using namespace std;

const int maxn=10005;
const int maxm=100005;

int n,m,size=0,ans,cnt;
int first[maxn];
int vis[maxn],vis1[maxn];
//vis用于标记联通块
//vis1用于深搜染色 
struct Edge
{
	int u,v,nt;//存入起点的目的是为了标记联通块和深搜
}edge[maxm<<1];

template<typename T>inline void read(T &x)
{
	x=0;char c=getchar();bool f=0;
	while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	if(f) x=-x;
}//读入优化 

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar((x%10)|48);
}

template<typename T>inline void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}//输出优化 

void docu()
{
	freopen("1330.txt","r",stdin);
}

void eadd(int u,int v)
{
	edge[++size].v=v;
	edge[size].u=u;
	edge[size].nt=first[u];
	first[u]=size;
}//链表存边 

void readdata()//读入数据 
{
	read(n);read(m);
	for(int i=1;i<=m;++i)
	{
		int x,y;
		read(x);read(y);
		eadd(x,y);
		eadd(y,x);
	}
}

bool dfs(int u)//我只搜要放河蟹的点 
{
	vis[u]=1;//标记联通块 
	vis1[u]=1;//标记已放了河蟹 
	++cnt;//cnt是已放河蟹的个数 
	for(int i=first[u];i;i=edge[i].nt)//枚举与u相连的不能放河蟹的点 
	{
		int v=edge[i].v;
		if(vis1[v]==1) {cnt=1000000;return 0;}//如果终点也放了河蟹,则这种方案是不可行的 
		vis1[v]=2;//标记终点未放河蟹但不能再放河蟹 
		for(int j=first[v];j;j=edge[j].nt)//枚举下一层要放河蟹的点 
		{
			int v1=edge[j].v;//因为v不能放河蟹,则v1必须放河蟹 
			if(vis1[v1]==1) continue;//已经有一个端点有了河蟹 
			if(vis1[v1]==2) {cnt=1000000;return 0;}//两个都不能再放河蟹,则这条边不能被覆盖,该方案不可行 
			if(!dfs(v1)) return 0;//若该方案不行 
		}
	}
	return 1;//若没有冲突 ,则此方案可行 
}

void work()
{
	ans=0;
	//注意,图不一定联通 ,所以要枚举边来标记联通块,这与我的深搜有关 
	for(int i=1;i<=(m<<1);i+=2)//枚举边,因为存的是无向边,所以m<<1(相当于m*2),
	 //又因为同一条边是连续存的两次故i+=2 
	{
		int v=edge[i].v;
		int u=edge[i].u;
		if(vis[u]||vis[v]) continue;//如果已经遍历过这个联通块 
		
		cnt=0; mem(vis1);//记得初始化 
		dfs(u);//假设起点放河蟹 
		int x=cnt;
		
		cnt=0;mem(vis1);
		dfs(v);//假设终点放河蟹 
		int y=cnt;
		
		if(x==1000000&&y==1000000)
		{//如果两种方法都不能覆盖所有的边,直接输出不可行 
			printf("Impossible");
			return;
		}
		ans+=Min(x,y);//选择两个方案中最小的 
	}
	put(ans);
}

int main()
{
//	docu();
	readdata();//模块化虽然程序稍显冗长,但是调试很方便
	work();
	return 0;
}
非做顽石不可,哪管他敬仰暗唾
原文地址:https://www.cnblogs.com/Mandy-H-Y/p/11354037.html