题目
题目描述
曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。
阳光大学的校园是一张由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;
}