• AGC008E:Next or Nextnext


    传送门
    考虑转化成图论问题,(i)(p_i) 连边,那么合法方案一定是形成了若干个简单环或自环
    考虑一个环内的情况:

    1. 如果 (a_i=p_i),那么 (i)(a_i) 连边的图和原图相比不变
    2. 如果 (a_i=p_{p_i})
      a. 环长为奇数且 (>1),那么 (i)(a_i) 连边的图仍然是一个环(不同)
      b. 环长为偶数,那么 (i)(a_i) 连边的图变成两个长度一样的环
      c. 环长为 (1),那么 (i)(a_i) 连边的图仍然是一个自环
    3. 那么 (i)(a_i) 连边的图成为一个基环内向森林

    (i)(p_i) 连边的图为原图,和 (a_i) 的为新图
    现在已知 (i)(a_i) 的边,求原图的方案数
    考虑一个环上的一条链,它还原成原图的环的方法只能是沿着环的逆时针方向插空还原
    因为新图的两个相邻点之间只能插入一个点
    那么有显而易见的几种无解的情况:

    1. 对于一个不属于环上的点,如果有两个及以上的点同时指向它,那么肯定不能还原成原图的环
    2. 对于一个属于环上的点,如果有两个及以上的点同时指向它,那么肯定也不能还原成原图的环
    3. 如果环上一条链 (a) 和环的沿逆时针方向的另一条链的距离小于 (a) 的长度,那么无解

    考虑计算答案:

    1. 对于新图的单个的环,把长度相同的一起 (dp),每次可以合并两个,或者奇数长度的同构等
    2. 对于基环内向树,如果环上一条链 (a) 和环的沿逆时针方向的另一条链的距离等于 (a) 的长度,那么只有一种方案,否则如果大于,就有两种,因为此时 (a) 的靠近环的点可以选择连上逆时针方向的一个点

    把这些东西互不影响,乘法原理即可

    # include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int maxn(1e5 + 5);
    const int mod(1e9 + 7);
    
    inline void Inc(int &x, int y) {
        x = x + y >= mod ? x + y - mod : x + y;
    }
    
    inline void Dec(int &x, int y) {
        x = x - y < 0 ? x - y + mod : x - y;
    }
    
    inline int Add(int x, int y) {
        return x + y >= mod ? x + y - mod : x + y;
    }
    
    inline int Sub(int x, int y) {
        return x - y < 0 ? x - y + mod : x - y;
    }
    
    int n, cir[maxn], cnt, fa[maxn], vis[maxn], len, d[maxn], in[maxn];
    int ans, f[maxn], a[maxn], chain[maxn], que[maxn << 1];
    
    inline void GetCircle() {
    	int i;
    	for (i = 1; i <= len; ++i) if (d[que[i]] ^ 1) return;
    	cir[++cnt] = len;
    	for (i = 1; i <= len; ++i) vis[que[i]] = 2;
    }
    
    void Dfs1(int u) {
    	int cur;
    	vis[u] = 1, in[u] = 1;
    	if (!vis[a[u]]) fa[a[u]] = u, Dfs1(a[u]);
    	else if (in[a[u]]) {
    		len = 0;
    		for (cur = u; ; cur = fa[cur]) {
    			que[++len] = cur, vis[cur] = 3;
    			if (cur == a[u]) break;
    		}
    		GetCircle();
    	}
    	in[u] = 0;
    }
    
    void Dfs2(int u) {
    	chain[a[u]] = chain[u] + 1;
    	if (vis[a[u]] > 1) return;
    	Dfs2(a[u]);
    }
    
    int Solve(int x) {
    	int cur, i, j, ret = 1;
    	que[len = 1] = x;
    	for (cur = a[x]; cur ^ x; cur = a[cur]) que[++len] = cur;
    	reverse(que + 1, que + len + 1), cur = len + len;
    	for (i = 1; i <= len; ++i) vis[que[i]] = 4, que[len + i] = que[i];
    	for (i = 1; i <= len; ++i)
    		if (chain[que[i]]) {
    			for (j = i + 1; j <= cur && !chain[que[j]]; ++j);
    			if (chain[que[i]] > j - i) puts("0"), exit(0);
    			if (chain[que[i]] < j - i) Inc(ret, ret);
    			i = j - 1;
    		}
    	return ret;
    }
    
    int main() {
    	int i, j, k, ret = 1;
    	scanf("%d", &n);
    	for (i = 1; i <= n; ++i) scanf("%d", &a[i]), ++d[a[i]];
    	for (i = 1; i <= n; ++i) if (!d[i]) Dfs1(i);
    	for (i = 1; i <= n; ++i) if (!vis[i]) Dfs1(i);
    	for (i = 1; i <= n; ++i)
    		if ((vis[i] > 1 && d[i] > 2) || (vis[i] == 1 && d[i] > 1)) return puts("0"), 0;
    	sort(cir + 1, cir + cnt + 1);
    	for (i = 1; i <= n; i = j) {
    		for (j = i; j <= n && cir[j] == cir[i]; ++j);
    		f[i - 1] = 1;
    		for (k = i; k < j; ++k) {
    			f[k] = f[k - 1];
    			if (cir[i] > 1 && (cir[i] & 1)) Inc(f[k], f[k - 1]);
    			if (k > i) Inc(f[k], (ll)f[k - 2] * (k - i) % mod * cir[i] % mod);
    		}
    		ret = (ll)ret * f[j - 1] % mod;
    	}
    	for (i = 1; i <= n; ++i) if (vis[i] == 1) vis[i] = 0;
    	for (i = 1; i <= n; ++i) if (!d[i]) Dfs2(i);
    	for (i = 1; i <= n; ++i) if (vis[i] == 3) ret = (ll)ret * Solve(i) % mod;
    	printf("%d
    ", ret);
    	return 0;
    }
    
  • 相关阅读:
    前端的一些小的效果
    数组排序
    查看字符串中出现次数最多的字符以及出现的次数
    让元素垂直水平居中的方式
    MySQL
    数组_集合转换
    Spring_使用(JDBC)
    Spring_Aop基于配置文件
    Spring_Aop_(二)
    Spring_总结
  • 原文地址:https://www.cnblogs.com/cjoieryl/p/10357895.html
Copyright © 2020-2023  润新知