题解 luogu1330 封锁阳光大学
作者:互联网
考虑到题中要覆盖到所有的边,则深搜+染色不失为一种好方法
认认真真(欢欢喜喜)的写了一个深搜准备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;
}
标签:封锁,河蟹,vis1,联通,标记,int,题解,cnt,luogu1330 来源: https://www.cnblogs.com/Mandy-H-Y/p/11354037.html