Always keep a |

luckydrawbox

园龄:4个月粉丝:1关注:2

UVA1108 Mining Your Own Business

Link\text{Link}

一定要看到最后哦!

题意

在一个无向图上选择尽量少的点涂黑,使得删除任意一个点后,每个连通分量里都至少有一个黑点。

分析

看到删除任意一个点和连通分量,不难想到先求无向图的点双连通分量并缩点。缩点之后,无向图就变成了一颗树(题目保证图是连通的)。

接下来我们对于每个缩成点后的点双联通分量进行分类讨论,此时原图变成了新图,设一个缩点包含的点集为 SS

  • SS 没有边与别的点相连。

显然,此时整个图是一个点双连通分量,我们可以把其中任意两个点(至少有两个点,因为题目保证 nn 是正整数)染成黑色,如果一个点被删了,显然连通分量中还是有黑点的。故此时最少的个数为 22SS 的方案数为 S×(S1)2\frac{|S|\times (|S|-1)}{2}

  • SS 只有一条边与别的点相连。

此时如果删掉这条边对面的点(易知对面的点为割点),SS 会从无向图中分裂出去,因此 SS 中至少有一个点被染黑;如果删掉 SS 中的黑点,则 SS 可与沿着该边到达的包含黑点的点集连通。

故最少的个数需要加上 11SS 的方案数为 S1|S|-1,减去 11 是因为 SS 中包含一个割点,把割点染黑是没有用处的,所以要减去。

  • SS 有多条边与别的点相连。

此时无论删除哪一个点,SS 都可与沿着这些边到达的包含黑点的点集连通,所以 SS 中不需要染黑点,最少的个数只要加上 00SS 的方案数为 11,相当于对答案没有任何影响。

综上所述,第一问的答案就是上面三种情况的最少个数之和,第二问的答案就是上面三种情况的方案数之积。

到了这里,你已经可以 AC 此题了,然而这还不够,你会发现这样好像过不了 AcWing 上的数据,原来 AcWing 上的点不保证全部都出现一遍,所以我们再开个数组 vv 表示每个点是否出现过,再计个数 mxmx 表示出现过的最大点,对于每个点 i(1imx)i(1\le i\le mx),如果出现过,则按照之前的方式处理,否则只会使第一问的答案增加 11

代码

时间复杂度 O(TN)O(TN)

#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;
}

别急着走哦!

突然

一位面带微笑的老伯伯(非作者)在你面前的屏幕中出现。

「小伙子,我看你在 @@chenjunxiu\color{Red}\text{chenjunxiu}\color{Blue}\surd 的题解前待很久了,是不是丢了什么东西?你丢的是这个金色的经验\color{Gold}\text{经验},还是这个银色的经验\color{Silver}\text{经验}呢?」

“老伯伯,我没有丢东西呀,我刚才只是在抄,呸,借鉴 Ta 的题解而已。”

「哦,真是个诚实的孩子,老夫决定把这两个\color{Gold}\text{经}\color{Silver}\text{验}都送给你!」

“az,谢谢老伯伯,不过,那几个有色字和两个带色的链接……您是怎样说出来的呢?”

「嗯……好问题,不过你得先点赞加关注,我才能告诉你哦。」

“……”

本文作者:luckydrawbox

本文链接:https://www.cnblogs.com/luckydrawbox/p/18526561

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   luckydrawbox  阅读(2)  评论(0编辑  收藏  举报  
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起