P11026 [COTS 2020] 抗疫 Autoritet 题解

前言

十分有趣的题。

推销博客园(划掉)

思路

首先看到这道题,它的连边就很神秘,考虑一下第一问是怎么个东西。

先考虑结束前(即构成一个联通块)之前是什么情况,先特判掉初始联通的情况,答案是 \(0\) 次然后 \(1\) 种可能。否则说明要么有一个独立点,它不与任何点连边,要么就是对于点 \(i\),选择之后它所在联通块仍然联通,即删除原来与 \(x\) 连接的边,在加上没有连接的,然后仍然联通,其它联通块显然也会和这个联通块联通(因为都有了和 \(x\) 的连边)。

所以我们的目标就是先把整个状态转化成上诉的。

那么考虑什么情况不满足上诉的,其实就是每一个联通块都是完全图,并且大小均 \(>1\)

注意到联通个数为 \(2\) 的完全图时,你操作一次之后还是会变成两个完全图,钦定最小联通块大小为 \(k\),第一问答案是 \(k-1\),第二问如果 \(k+k=n\),那么答案是 \(2\times k!\),否则是 \(k!\),因为每个点都是等价点。

否则联通块个数大于二,操作一次 \(x\) 之后,\(x\) 虽然不能选,但与 \(x\) 原来无连边的点都可以选了,所以第一问答案为 \(2\),然后一个大小为 \(k\) 的联通块会有 \(k\times(n-k)\) 的贡献,特别的,联通块大小为 \(2\) 时答案额外加二,即选择独立点。

然后考虑初始状态就是可以选一个合法的,第一问答案为 \(1\),然后考虑第二问本质是求什么,首先独立点可以选,然后对于一个联通块,如果所有非 \(x\) 点,不经过 \(x\) 能到达一个与 \(x\) 无连边的点,那么 \(x\) 就可以选(即选完后这个联通块仍然联通),不经过 \(x\),我们考虑圆方树,然后就是以 \(x\) 为根,每一个儿子(方点)的子树都至少有一个点在原图上与 \(x\) 没有连边,这里提供一个 \(m\log n\) 的做法,我们考虑求出 dfs 序,然后对于 \(x\) 点暴力看与之相连的边,然后看是在哪个子树里,最后看每个子树是不是每个圆点全都是与 \(x\) 相连即可,具体实现见代码。

code

#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace IO
{
	template<typename T>
	void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
	template<typename T,typename... Args>
	void read(T &_x,Args&...others){Read(_x);Read(others...);}
	const int BUF=20000000;char buf[BUF],top,stk[32];int plen;
	#define pc(x) buf[plen++]=x
	#define flush(); fwrite(buf,1,plen,stdout),plen=0;
	template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++top]=48+x%10;while(top) pc(stk[top--]);}
}
using namespace IO;
const int N = 1e6+10,mod = 1e9+7;
int n,m,x,y,head[N],cnt,f[N],siz[N],in[N],ans,sum,o,mi;
int dfn[N],low[N],cnt1,cnt2,T[N],T1[N],S[N],S1[N],len,dw;
map<pair<int,int>,bool>mp;
vector<int>v[N];
struct w
{
	int to,nxt;
}b[N<<1];
inline void add(int x,int y)
{
	b[++cnt].nxt = head[x];
	b[cnt].to = y;
	head[x] = cnt;
}
int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);
}
void tarjan(int x,int y)
{//这里保证联通块个数>1 
	dfn[x] = low[x] = ++cnt1; T[++cnt2] = x; len++;
	for(int i = head[x];i;i = b[i].nxt)
		if(!dfn[b[i].to]) 
		{
			tarjan(b[i].to,y),low[x] = min(low[x],low[b[i].to]);
			if(low[b[i].to] >= dfn[x])//不经过x,就是不能到x 
			{ o++;
				while(T[cnt2+1] != b[i].to) v[o].push_back(T[cnt2]),v[T[cnt2]].push_back(o),cnt2--;
				//取出来 
				v[o].push_back(x),v[x].push_back(o);
			}
		}
		else low[x] = min(low[x],dfn[b[i].to]); 
}
void dfs(int x,int y)
{
	dfn[x] = ++cnt2; S1[x] = 1;
	if(x <= n) S[x] = 1;
	for(int i = 0;i < v[x].size();i++)
		if(v[x][i] != y) dfs(v[x][i],x),S1[x] += S1[v[x][i]],S[x] += S[v[x][i]];
}
struct w1
{
	int x,y,z;
}a[N];
inline bool cmp(w1 x,w1 y){ return x.x < y.x; }
void dfs1(int x,int y)
{
	for(int i = 0;i < v[x].size();i++)
		if(v[x][i] != y) dfs1(v[x][i],x);
	if(x <= n)//直接一只log搞算了 
	{ cnt2 = 0;//偷过来用 
		dw = 0;//小林檎 
		for(int i = 0;i < v[x].size();i++) 
			if(v[x][i] != y) a[++cnt2].x = dfn[v[x][i]],a[cnt2].y = S[v[x][i]],a[cnt2].z = 0;
		sort(a+1,a+1+cnt2,cmp);
		for(int i = 1;i <= cnt2;i++) T[i] = a[i].x;
		T[++cnt2] = 1e15;
		for(int i = head[x];i;i = b[i].nxt)
		{
			if(dfn[b[i].to] < dfn[x] || dfn[b[i].to] > dfn[x]+S1[x]-1) dw++;//外面那个点 
			else a[lower_bound(T+1,T+1+cnt2,dfn[b[i].to]+1)-T-1].z++;
		}
		if(y != 0 && dw == len-S[x]) return;
		for(int i = 1;i < cnt2;i++)
			if(a[i].y == a[i].z) return;
		ans++;
	}
}
signed main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	read(n),read(m);
	for(int i = 1;i <= n;i++) siz[i] = 1,f[i] = i;
	for(int i = 1;i <= m;i++)
	{
		read(x),read(y);
		if(x > y) swap(x,y);
		if(mp[make_pair(x,y)]) continue;
		mp[make_pair(x,y)] = 1;
		add(x,y),add(y,x); in[x]++,in[y]++;
		if(find(x) != find(y)) siz[find(y)] += siz[find(x)],f[find(x)] = find(y);
	}
	if(siz[find(1)] == n)
	{
		print(0),pc('\n'),print(1);
		flush();
		return 0;
	} o = 0;
	for(int i = 1;i <= n;i++)
		if(siz[find(i)] == 1){ o = 1; break; }
		else if(siz[find(i)]-1 != in[i]){ o = 1; break; }
	if(o == 0)
	{
		for(int i = 1;i <= n;i++)
			if(find(i) == i) o++;
		if(o == 2)
		{ mi = n+10;
			for(int i = 1;i <= n;i++) mi = min(mi,siz[find(i)]);
			print(mi),pc('\n'); 
			ans = 1;
			for(int i = 1;i <= mi;i++) ans = ans*i%mod;
			if(mi*2 == n) ans = ans*2%mod;//两边都可以
			print(ans);
			flush();
			return 0; 
		}
		else//>2个联通块 
		{
			for(int i = 1;i <= n;i++)
				if(find(i) == i) 
				{
					o = siz[i];
					if(o == 2) ans = (ans+2*(n-1))%mod;//选其中一个点,剩下都能选
					else ans = (ans+o*(n-o))%mod; 
				}
			print(2),pc('\n');
			print(ans); flush();
			return 0;
		}
	}
	/*
	考虑什么点可以:
	建出圆方树,x相邻的方点,都必须有至少一个儿子与x每连边
	不经过x,那么就是圆方树上不经过x随便走
	然后那个子树有,才能使得联通
	考虑先建圆方树
	然后dfs跑的时候,判一下就好了 
	*/ 
	o = n; 
	for(int i = 1;i <= n;i++)
		if(find(i) == i)
		{
			if(siz[i] == 1) ans++;
			else
			{cnt1 = cnt2 = len = 0;
				tarjan(i,0);
				cnt2 = 0,dfs(i,0);
				dfs1(i,0);
			} 
		}
	print(1),pc('\n');
	print(ans); flush();
	return 0;
}
posted @ 2025-10-26 23:15  kkxacj  阅读(1)  评论(0)    收藏  举报