zoukankan      html  css  js  c++  java
  • 题解-AtCoder ARC-083F Collecting Balls

    Problem

    ARC083F

    题意概要:给定 (2n) 个二维平面上的球,坐标分别为 ((x_i,y_i)),并给出 (n)(A)类 机器人 和 (n)(B)类 机器人,其中:

    • (A)加5分 机器人分布在横坐标上,坐标依次为 ((1,0),(2,0),cdots ,(n,0)),触发第 (i) 个机器人,它会将位处第 (i) 列的最下头的球拿走(即横坐标为 (i) 且纵坐标最小的球)
    • (B)不加分 机器人分布在横坐标上,坐标依次为 ((0,1),(0,2),cdots ,(0,n)),触发第 (i) 个机器人,它会将位处第 (i) 行的最左侧的球拿走(即纵坐标为 (i) 且横坐标最小的球)

    现在需要依次触发这 (2n) 个机器人,(每个机器人只能被触发一次),问有多少种触发机器人的方式能将所有球拿完(共 ((2n)!) 种可能情况)

    (2le nle 10^5, 1le x_i,y_ile n)

    Solution

    做完这道毒瘤题,17年的作业终于完成了! (。・∀・)ノ゙

    往上一翻发现自己是从一半开始做的 இ皿இ

    乍一看没有啥子想法

    考虑到一个球 ((x,y)),它有两种被清除的方式:被第 (x)(A) 給除掉;被第 (y)(B) 給除掉。而同时,由于球的数量和机器人的数量相同,所以每个机器人必须认领一个球。

    这乍一看没啥子用,但放在图上就是个基环树森林:

    • 将图给构出来:对于每个球 ((x,y)),在 (x)(y') 间连边。
    • 由于每个机器人必须认领一个球,所以可视作每个点必须认领一条边。可以发现如果这个图不是基环树森林,则整个题根本无法满足。
    • 由于每个球需要被一个机器人捡拾,所以需要给基环树森林的每条边定向,使得每个点只有一条入边(一个点的入边即它认领的边,即 “一种合法的定向方案” 对应着 “一种合法的球和机器人的匹配方式”)。
    • 为了满足题目中所描述的“机器人只会拿离自己最近的球”的限制,对于(A)类机器人 ((x,0)) 去拿球 ((x,y)),需要(B)类机器人将 ((x,t),tin[1,y)) 上的球清理干净先,所以这些 (B) 类 机器人需要比这个 (A)类机器人更早被触发。

    原题转化为:对于每种给森林定向的方案,求出其满足上述拓扑关系的排列个数。


    先考虑对于某种定向方案,求出合法排列方案。

    如果对于这种拓扑关系连边,最终这些拓扑序关系的边将组成一个内向树森林:

    • 若负责拿取球 ((x_1,y_1)) 机器人 是 负责拿取球 ((x_2,y_2)) 机器人 的先决条件,则其至少需要满足 (x_1+y_1<x_2+y_2),这证实了整个图应该是一个 (DAG)
    • 每个 (B)类机器人只可能是一个 (A)类机器人的先决条件,所以每个点只有一条出边。
    • 综上:这是一个内向树森林。

    对于内向树森林求拓扑序的部分,应该都很熟悉了:设森林点集为 (S),节点 (i) 的子树大小为 (sz_i),则这个内向树森林的拓扑序个数为

    [frac {|S|!}{prod_{xin S}sz_x} ]

    简要说一下证明:可以将Dp式列出来,将每个节点 (x) 在自身计算的系数 ((sz_x-1)!) 与在其父亲 (f) 处计算的 (frac 1{sz_x!}) 抵消,可得 (frac 1{sz_x}),综合可得上述公式。森林的话,考虑拿一个虚根将所有树串起来即可。


    解决完了求方案数的任务,再考虑枚举定向方案。

    枚举所有定向方案肯定是不可取的,但幸运的是,这是一个基环树森林。

    对于一棵基环树而言,其非环边只能由远离环的点认领,而环则有顺逆两种方式认领,可以暴力枚举。

    由幼儿园学习的乘法分配率可知,不同的树之间没必要一起枚举,所以对于每棵树算出两种定向方式的答案和,再将所有树的答案乘起来即可。


    时间复杂度明显是线性,顶多是枚举每棵树的两种定向方式时有个 (2) 的常数

    Code

    #include <cstdio>
    #include <cctype>
    typedef long long ll;
    
    template <typename _tp> inline void read(_tp&x){
    	char ch=getchar(),ob=0;x=0;
    	while(ch!='-'&&!isdigit(ch))ch=getchar();if(ch=='-')ob=1,ch=getchar();
    	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();if(ob)x=-x;
    }
    
    const int N = 201000;
    struct Edge {int v, nxt;} a[N*4];
    int head[N], Head[N], indeg[N];
    int sz[N], pr[N];
    bool vis[N];
    int n, _;
    
    inline void add(int x, int y, int*arr) {
    	a[++_].v = y, a[_].nxt = arr[x], arr[x] = _;
    }
    
    const int p = 1e9+7;
    int fac[N], ifac[N], inv[N];
    
    int st[N], tp;
    #define FID(i) for(int id=1,i;i=st[id],id<=tp;++id)
    
    int X, Y;
    int et, pt;
    
    void bfs(int x, int las) {
    	vis[st[++tp] = x] = true, ++pt;
    	for(int i=head[x];i;++et,i=a[i].nxt)
    		if(!vis[a[i].v]) bfs(a[i].v, x);
    		else if(a[i].v != las) X = x, Y = a[i].v;
    }
    
    void tfs(int x, int las) {
    	for(int i=head[x];i;i=a[i].nxt)
    		if(a[i].v != las and a[i].v != X)
    			pr[a[i].v] = x, tfs(a[i].v, x);
    }
    
    void dfs(int x, int las) {
    	sz[x] = 1;
    	for(int i=Head[x];i;i=a[i].nxt)
    		if(a[i].v != las)
    			dfs(a[i].v, x), sz[x] += sz[a[i].v];
    }
    
    int solve() {
    	tfs(X, Y);
    	pr[X] = Y;
    	FID(i) Head[i] = indeg[i] = 0;
    	FID(x) for(int i=head[x];i;i=a[i].nxt)
    		if(a[i].v < pr[x]) add(x, a[i].v, Head), ++indeg[a[i].v];
    	
    	FID(i) if(!indeg[i]) dfs(i, i);
    	int Ans = fac[pt];
    	FID(i) Ans = (ll)Ans * inv[sz[i]]%p;
    	return Ans;
    }
    
    int main() {
    	read(n);
    	for(int i=1,x,y;i<=(n<<1);++i) {
    		read(x), read(y), y += n;
    		add(x, y, head), add(y, x, head);
    	}
    	
    	n <<= 1;
    	fac[0] = fac[1] = inv[0] = inv[1] = ifac[0] = ifac[1] = 1;
    	for(int i=2;i<=n;++i) {
    		fac[i] = (ll)fac[i-1] * i%p;
    		inv[i] = (ll)(p-p/i) * inv[p%i]%p;
    		ifac[i] = (ll)ifac[i-1] * inv[i]%p;
    	}
    	
    	int Ans = fac[n];
    	for(int i=1;i<=n;++i)
    		if(!vis[i]) {
    			tp = pt = et = 0;
    			bfs(i, i);
    			Ans = (ll)Ans * ifac[pt]%p;
    			if((pt << 1) != et) return puts("0"), 0;
    			int res = solve();
    			X ^= Y, Y ^= X, X ^= Y;
    			res += solve();
    			Ans = (ll)Ans * res%p;
    		}
    	printf("%d
    ", Ans);
    	return 0;
    }
    
  • 相关阅读:
    JS两个页面通过URL传值
    新起点 新开始
    Spring Boot 常见标签
    关于Redis缓存数据库
    JPA问题汇总
    Dynamic 报表服务开发
    Dynamic crm自定义页面
    Dynamic 根据用户的角色权限设置相应的按钮显示
    Dynamic 工具类
    Dynamic 点击按钮,弹出一个漂浮页面
  • 原文地址:https://www.cnblogs.com/penth/p/11447813.html
Copyright © 2011-2022 走看看