[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';
}
[ARC083F] Collecting Balls [建二分图+环套树定向+建拓扑图+树的拓扑序计数]的更多相关文章
- 题解-AtCoder ARC-083F Collecting Balls
Problem ARC083F 题意概要:给定 \(2n\) 个二维平面上的球,坐标分别为 \((x_i,y_i)\),并给出 \(n\) 个 \(A\)类 机器人 和 \(n\) 个 \(B\)类 ...
- Arc083_F Collecting Balls
传送门 题目大意 给定$N$,在$(1,0),(2,0)......(N,0)$和$(0,1),(0,2)...(0,N)$上都有$1$个机器人,同时给定$2N$个坐标$(x,y),x,y\in[1, ...
- Codeforces 1045. A. Last chance(网络流 + 线段树优化建边)
题意 给你 \(n\) 个武器,\(m\) 个敌人,问你最多消灭多少个敌人,并输出方案. 总共有三种武器. SQL 火箭 - 能消灭给你集合中的一个敌人 \(\sum |S| \le 100000\) ...
- HDU 5669 线段树优化建图+分层图最短路
用线段树维护建图,即把用线段树把每个区间都标号了,Tree1中子节点有到达父节点的单向边,Tree2中父节点有到达子节点的单向边. 每次将源插入Tree1,汇插入Tree2,中间用临时节点相连.那么T ...
- UOJ#77. A+B Problem [可持久化线段树优化建边 最小割]
UOJ#77. A+B Problem 题意:自己看 接触过线段树优化建图后思路不难想,细节要处理好 乱建图无果后想到最小割 白色和黑色只能选一个,割掉一个就行了 之前选白色必须额外割掉一个p[i], ...
- CF786B Legacy(线段树优化建图)
嘟嘟嘟 省选Day1T2不仅考了字符串,还考了线段树优化建图.当时不会,现在赶快学一下. 线段树能优化的图就是像这道题一样,一个点像一个区间的点连边,或一个区间像一个点连边.一个个连就是\(O(n ^ ...
- [十二省联考2019]字符串问题——后缀自动机+parent树优化建图+拓扑序DP+倍增
题目链接: [十二省联考2019]字符串问题 首先考虑最暴力的做法就是对于每个$B$串存一下它是哪些$A$串的前缀,然后按每组支配关系连边,做一遍拓扑序DP即可. 但即使忽略判断前缀的时间,光是连边的 ...
- BZOJ5017 [SNOI2017]炸弹 - 线段树优化建图+Tarjan
Solution 一个点向一个区间内的所有点连边, 可以用线段树优化建图来优化 : 前置技能传送门 然后就得到一个有向图, 一个联通块内的炸弹可以互相引爆, 所以进行缩点变成$DAG$ 然后拓扑排序. ...
- bzoj5017 炸弹 (线段树优化建图+tarjan+拓扑序dp)
直接建图边数太多,用线段树优化一下 然后缩点,记下来每个点里有多少个炸弹 然后按拓扑序反向dp一下就行了 #include<bits/stdc++.h> #define pa pair&l ...
随机推荐
- Core Location :⽤用于地理定位
Core Location :⽤用于地理定位 在移动互联⽹网时代,移动app能解决⽤用户的很多⽣生活琐事,⽐比如 导航:去任意陌⽣生的地⽅方 周边:找餐馆.找酒店.找银⾏行.找电影院 在上述应⽤用中, ...
- windows 编译安卓iconv 库
由于NDK r15后,谷歌要统一将来的设备都要支持64位,而iconv只支持32位,后续的ndk都会去除iconv的支持,所以只能在iconv的官网下载源码编译库文件使用, 下载地址:https:// ...
- html5 canvas中CanvasGradient对象用法
html5 中canvas提供了强大的渲染样式,可以实现一些比较复杂的样式设置,今天学习了CanvasGradient对象可以实现一个颜色的渐变 CanvasGradient对象可以实现两种不同形式的 ...
- 灵光一现的trick
感觉平时会丢掉好多挺好的trick…… 图论 1.图G,固定S,T.可以将任意一条边加上权值$k(k>0)$,求最大化加权后最短路. 2.图G,固定S,T.可以将任意一条边乘以权值$k(k> ...
- Python全栈day 05
Python全栈day 05 一.数据类型补充 1. int py2和py3的2种区别 py2有int和long,int的取值范围为-2^31~2^31-1,超出范围自动转为long,长整型. py2 ...
- linux基础命令3(man)
Type:显示指定的命令是那种类型. Linux下有两种模式的时间 date:用于系统时间管理.(软件操作的系统时 ...
- Sqlite客户端的使用
打开一个数据库sqlite3 ${databaseName} 查看当前打开的数据库.database 查看当前打开的数据库中的表.table 查看指定表结构(实际输出是建表语句).schema ${t ...
- PHP.TP框架下商品项目的优化2-图片优化
图片存储.上传.显示优化 1.图片路径写进配置文件,当路径有变动时[因业务扩大,服务器存储图片空间不足等],只需修改配置文件,而不用修改代码 2.封装显示.上传.删除函数,实现代码重用 [可类比其他类 ...
- 16 Django-admin管理工具
admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.你可以在项目的 settings.py 中的 INSTAL ...
- 十二、mysql之视图,触发器,事务等
一.视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,可以将该结果集当做表来使用. 使用视图我们可以把查询过程中的 ...