[ARC083F] Collecting Balls [建二分图+环套树定向+建拓扑图+树的拓扑序计数]
题面
[传送门](https://arc083.contest.atcoder.jp/tasks/arc083_d)
思路
这是一道真正的好题
第一步:转化模型
行列支配类的问题,常见做法就是把行和列变成二分图中的点,把矩阵内元素作为边,转化为图论问题
本题中,我们把第$(i,j)$格子中的球,变成连接$i$行和$j$列的无向边即可
容易发现,对于不同的联通块之间,子问题互相没有影响,因此可以对于每个块分别处理
第二步:联通块性质
观察可得,若任何一个联通块的点数和边数不相等,那么题目无解
若满足这个条件,显然所有的联通块都是环套树的形态
再次考虑本题的原来意义,可以发现,我们就是要对每一个点,找一条和它相邻的边,让这个点支配这条边
那么在环套树意义下,显然环上的边只能由环上点来支配,树上边只能由非环点来支配
这样,问题的解即为树上的点支配它连向父亲的边,而环上点依次顺时针或者逆时针支配环上的边
每个联通块有且只有两个解
考虑上述解,显然可以把“点支配边”的过程理解为给边重定向,也就是说这个过程之后我们得到了一个基环内向树,环内边可能是顺时针或者逆时针
第三步:判断解的操作顺序及其合法性
考虑题目中的一个限制:如果$u$行的点想要支配$(u,v)$位置的小球,那么所有位于$(u,w)(1\leq w < v)$的小球都必须先被其他$w$列支配才行
发现这个限制,如果把整个联通块的限制放到一起,会形成一个DAG,表示支配关系
同时易证这个DAG一定是一棵树或者一个森林(支配关系不能被同步影响再同步影响其它点)
那么,设对于点$u$而言,无向环套树上与它相连的点为${v_1,v_2,v_3...v_k}$,这个点集是从小到大排好序的
如果点$u$选择支配边$(u,v_i)$,那么我们应当在支配DAG上,连边$(v_j,u)(1\leq j < i)$
这样,我们会得到一个支配森林,森林中的边是从儿子到父亲的有向DAG边
第四步:计算答案
对于一棵支配树,显然这个支配树的问题来源的那棵环套树的答案,就是这棵树的拓扑序数量
树的拓扑序数量计算方式为$Ans=\frac{siz(root)!}{\prod siz(u)}$,其中$root$为根,$u$为任意节点,结论证明点这里
对于一个森林,计算它的拓扑序数量如下:
$Ans=\frac{(\sum siz(root_i))!}{\prod siz(u)}$,其中$root_i$为森林中树的根,$u$为任意节点,证明同上
本题中,如果我们确定了每个联通块的环套树的环上定向方向,那么所有联通块的基环内向树的生成支配森林构成的大森林的拓扑序数量即为这种定向方案的答案
但是,如果用2的联通块个数次方种定向方式来加起来计算答案,显然时间上难以接受
因此我们考虑把这一步加法原理放到中间过程中处理
第五步:优化计算
考虑到这个加法原理的本质,是每个联通块可以有两种不同的生成方式
那么我们只需要把加法原理挪到这里即可,最终的答案计算方法如下:
对于某一个联通块的基环内向树生成的两种森林(分别对应两种环上定向方式),求出森林的拓扑序数量的分母,记为$A$
注意这里相当于求$A=\frac{1}{\prod siz(u)}$,其中$root_i$为森林中树的根,$u$为任意节点
然后,对于一个联通块,把两个$A$相加得到$B$
对于总问题,把每个联通块的$B$乘起来,再乘以$2n!$,就得到了答案
这个过程实际上是把森林到联通块之间构成的总森林的过程,视为树到森林的拓扑序数量计算合并过程
中间用了一步加法原理
总结
遇到Atcoder题,想不出来的时候,请试一试建图
如果建图成功但是做不出来,想想环套树吧
(上面两条是玄学)
毋庸置疑,这是一道好题,题目中一共有了3个问题转化,以及两处子问题分治,子问题和模型之间也有互相的影响、结论
解决这类题目的关键,在于及时把握好子模型在原问题中的意义,适时引入原问题结论,得到子问题结论
但是同时也要注意不能太过依赖于原模型的结论,毕竟新建立的子模型,也具有模型本身的一些结论可以加以利用
Code
拒绝压行,从我做起
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<stack>
#include<vector>
#define MOD 1000000007
#define ll long long
using namespace std;
inline int read(){
int re=0,flag=1;;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') flag=-1;
ch=getchar();
}
while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
ll inv[200010],fac[200010];
namespace topo{
int first[200010],cnte;
vector<int>e[200010];
int inbound[200010],worked[200010];
vector<int>appearance;
inline void add(int u,int v){
e[u].push_back(v);
inbound[v]++;
}
int siz[200010];
ll dfs(int u,int f){
ll re=1;siz[u]=1;
assert(u);
for(auto v:e[u]){
if(v==f) continue;
(re*=dfs(v,u))%=MOD;
siz[u]+=siz[v];
}
return re*inv[siz[u]]%MOD;
}
ll solve(){
ll re=1,tmp;
for(auto u:appearance){
if(!inbound[u]&&!worked[u]){
tmp=dfs(u,0);
worked[u]=1;
(re*=tmp%MOD)%=MOD;
}
}
for(auto u:appearance){
e[u].clear();
inbound[u]=0;
worked[u]=0;
}
return re;
}
}
vector<int>e[200010];
stack<int>s;
bool vis[200010],on_circle[200010];int cnt1,cnt2,lped;
vector<int>circle;
void dfs(int u,int f){
int i,v;cnt1++;s.push(u);
vis[u]=1;
for(i=0;i<e[u].size();i++){
v=e[u][i];if(v==f||on_circle[v]) continue;
cnt2++;
if(!lped&&vis[v]){
lped=1;
while(s.top()!=v){
circle.push_back(s.top());
on_circle[s.top()]=1;
s.pop();
}
circle.push_back(s.top());
on_circle[s.top()]=1;
s.pop();
}
else if(!vis[v]){
dfs(v,u);
}
}
if(!s.empty()&&s.top()==u) s.pop();
}
int control[200010];
void dfs2(int u,int f){
int i,v;
for(i=0;i<e[u].size();i++){
v=e[u][i];
if(v==f||on_circle[v]) continue;
control[v]=u;
dfs2(v,u);
}
}
void adde(int u,int f){
topo::appearance.push_back(u);
for(auto v:e[u]){
if(v==control[u]) break;
topo::add(u,v);
}
for(auto v:e[u]){
if(v==f||on_circle[v]) continue;
adde(v,u);
}
}
ll solve(int u){
cnt1=cnt2=lped=0;
dfs(u,0);
if(cnt1!=cnt2) return 0;
int i;ll re=0;
for(i=0;i<circle.size();i++){
dfs2(circle[i],0);
}
//first
for(i=0;i<circle.size();i++){
control[circle[i]]=circle[(i+1)%circle.size()];
}
for(i=0;i<circle.size();i++){
adde(circle[i],0);
}
re+=topo::solve();
topo::appearance.clear();
//second
for(i=0;i<circle.size();i++){
control[circle[i]]=circle[(i-1+circle.size())%circle.size()];
}
for(i=0;i<circle.size();i++){
adde(circle[i],0);
}
re+=topo::solve();
topo::appearance.clear();
for(i=0;i<circle.size();i++) on_circle[i]=0;
circle.clear();
return re%MOD;
}
int n;
int main(){
n=read();int i,t1,t2;
memset(topo::first,-1,sizeof(topo::first));
inv[1]=1;fac[1]=1;
for(i=2;i<=n*2;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
for(i=2;i<=n*2;i++) fac[i]=fac[i-1]*i%MOD;
for(i=1;i<=n*2;i++){
t1=read();t2=read();t2+=n;
e[t1].push_back(t2);
e[t2].push_back(t1);
}
for(i=1;i<=n*2;i++){
if(e[i].size()) sort(e[i].begin(),e[i].end());
}
ll ans=1;
for(i=1;i<=n*2;i++){
if(!vis[i]) (ans=ans*solve(i))%=MOD;
}
cout<<(fac[2*n]*ans)%MOD<<'\n';
}