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;
}
浙公网安备 33010602011771号