UVA1108 Mining Your Own Business
一定要看到最后哦!
题意
在一个无向图上选择尽量少的点涂黑,使得删除任意一个点后,每个连通分量里都至少有一个黑点。
分析
看到删除任意一个点和连通分量,不难想到先求无向图的点双连通分量并缩点。缩点之后,无向图就变成了一颗树(题目保证图是连通的)。
接下来我们对于每个缩成点后的点双联通分量进行分类讨论,此时原图变成了新图,设一个缩点包含的点集为 。
- 没有边与别的点相连。
显然,此时整个图是一个点双连通分量,我们可以把其中任意两个点(至少有两个点,因为题目保证 是正整数)染成黑色,如果一个点被删了,显然连通分量中还是有黑点的。故此时最少的个数为 , 的方案数为 。
- 只有一条边与别的点相连。
此时如果删掉这条边对面的点(易知对面的点为割点), 会从无向图中分裂出去,因此 中至少有一个点被染黑;如果删掉 中的黑点,则 可与沿着该边到达的包含黑点的点集连通。
故最少的个数需要加上 , 的方案数为 ,减去 是因为 中包含一个割点,把割点染黑是没有用处的,所以要减去。
- 有多条边与别的点相连。
此时无论删除哪一个点, 都可与沿着这些边到达的包含黑点的点集连通,所以 中不需要染黑点,最少的个数只要加上 , 的方案数为 ,相当于对答案没有任何影响。
综上所述,第一问的答案就是上面三种情况的最少个数之和,第二问的答案就是上面三种情况的方案数之积。
到了这里,你已经可以 AC 此题了,然而这还不够,你会发现这样好像过不了 AcWing 上的数据,原来 AcWing 上的点不保证全部都出现一遍,所以我们再开个数组 表示每个点是否出现过,再计个数 表示出现过的最大点,对于每个点 ,如果出现过,则按照之前的方式处理,否则只会使第一问的答案增加 。
代码
时间复杂度 。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=1e5+10;
int t,n,mx;
ll ans,sum;
int head[N],nxt[N<<1],ver[N<<1],tot;
int hc[N],nc[N<<1],vc[N<<1],tc;
int root,num,dfn[N],low[N];
int top,stak[N];
int cnt,new_id[N],c[N];
vector<int>v_dcc[N];
bool cut[N],v[N];
void add(int x,int y){
ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
}
void add_c(int x,int y){
vc[++tc]=y,nc[tc]=hc[x],hc[x]=tc;
}
void tarjan_v_dcc(int x){
dfn[x]=low[x]=++num;
stak[++top]=x;
int flag=0;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(!dfn[y]){
tarjan_v_dcc(y);
low[x]=min(low[x],low[y]);
if(dfn[x]<=low[y]){
flag++;
if(x!=root||flag>1)
cut[x]=1;
cnt++;
int z;
do{
z=stak[top--];
v_dcc[cnt].push_back(z);
}while(z!=y);
v_dcc[cnt].push_back(x);
}
}
else
low[x]=min(low[x],dfn[y]);
}
}
void work_v_dcc(){
for(int i=1;i<=mx;i++)
if(v[i]&&!dfn[i])
root=i,tarjan_v_dcc(i);
}
void sh_v_dcc(){
num=cnt;
for(int i=1;i<=mx;i++)
if(cut[i])
new_id[i]=++num;
tc=1;
for(int i=1;i<=cnt;i++)
for(int j=0;j<v_dcc[i].size();j++){
int x=v_dcc[i][j];
if(cut[x]){
add_c(i,new_id[x]);
add_c(new_id[x],i);
}
else
c[x]=i;
}
}
int main(){
while(n=read()){
printf("Case %d: ",++t);
cnt=top=num=mx=0;//初始化
tot=1;
memset(head,0,sizeof(head));
memset(hc,0,sizeof(hc));
memset(cut,0,sizeof(cut));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(stak,0,sizeof(stak));
memset(v,0,sizeof(v));
sum=0;ans=1;
for(int i=1,x,y;i<=n;i++){//读入
x=read();y=read();
add(x,y);add(y,x);
v[x]=v[y]=1;
mx=max(mx,max(x,y));
}
for(int i=1;i<=mx;i++)
v_dcc[i].clear();
work_v_dcc();//求点双连通分量
sh_v_dcc();//缩点建图
for(int i=1;i<=cnt;i++){//分类求答案
if(!hc[i]){//第一种
sum+=2;
ans*=(ll)v_dcc[i].size()*(v_dcc[i].size()-1)/2;
}
else if(!nc[hc[i]]){//第二种
sum++;
ans*=(ll)(v_dcc[i].size()-1);
}
}
for(int i=1;i<=mx;i++)
if(!v[i])
sum++;
printf("%lld %lld\n",sum,ans);
}
return 0;
}
别急着走哦!
突然
一位面带微笑的老伯伯(非作者)在你面前的屏幕中出现。
「小伙子,我看你在 的题解前待很久了,是不是丢了什么东西?你丢的是这个金色的,还是这个银色的呢?」
“老伯伯,我没有丢东西呀,我刚才只是在抄,呸,借鉴 Ta 的题解而已。”
“az,谢谢老伯伯,不过,那几个有色字和两个带色的链接……您是怎样说出来的呢?”
「嗯……好问题,不过你得先点赞加关注,我才能告诉你哦。」
“……”
本文作者:luckydrawbox
本文链接:https://www.cnblogs.com/luckydrawbox/p/18526561
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步