• CF1466H Finding satisfactory solutions


    题目链接

    题意简述

    (n)(1)(n) 的排列,第 (i) 个排列为 (P_i)

    定义一个排列 (A) 是最优的,当且仅当不存在任意一个排列 (B) 满足存在一个下标集合 (S) 符合以下所有条件:

    (1)(forall iin S)(B_iin S)

    (2)(forall iin S),在 (P_i)(A_i) 不能出现在 (B_i) 的左边。

    (3)(exists iin S),在 (P_i)(B_i) 出现在 (A_i) 的左边。

    给定一个排列 (A),求有多少种 (P_{1dots n}) 使得排列 (A) 是最优的,对 (10^9+7) 取模。

    (1le nle 40)

    Solution

    显然在 (P_i) 中只有出现在 (A_i) 及之前的数是有用的。

    考虑建一张有向图:如果在 (P_i)(j) 出现的位置严格在 (A_i) 出现的位置左边,则连白边 ((i,j)),然后对于所有的 (i)黑边 ((i,A_i))

    对应到排列 (A) 不是最优的条件,相当于可以选出若干个不相交的环(符合了条件 (1)),满足这些环的边不全为黑(符合了条件 (3))。

    到这里可以发现其实没有必要选若干个不相交的环,只要能选一个不全为黑边的环出来,(A) 就不是最优的了。

    考虑一个强连通分量,不难发现只要分量内有白边,就一定能选出一个不全为黑边的环出来。

    于是我们得到 (A) 是最优的条件:图的每个强连通分量内都只有黑边。

    由于所有的黑边 ((i,A_i)) 已经给出,并且所有黑边构成了若干个不相交的环,故把这些环当成一个大点,要求的就是把这些大点连成一个 DAG 的方案数。

    同时由于 (P_i) 是排列,故如果 (i) 点出发的白边有 (d) 条,则会带上 (d!(n-d-1)!) 的乘积贡献(把 (P_i)(A_i) 之前和之后的数安排顺序)。

    考虑暴力状压 DP:(f_S) 表示把集合 (S) 内的大点连成 DAG 的方案数。

    转移考虑枚举入度为 (0) 的点集 (T),加上点集 (T) 连向点集 (S-T) 的方案数乘上 (f_{S-T})

    这样不能保证 (S-T) 内没有入度为 (0) 的点,故还要套一个容斥:设上面算出的结果为 (g_T),则真正入度为 (0) 的点集恰好为 (T) 的方案数应为 (sum_{Tsubseteq Usubseteq S}(-1)^{|U|-|T|}g_U)

    于是:

    [f_S=sum_{T eemptyset,Tsubseteq Usubseteq S}(-1)^{|U|-|T|}g_U=sum_{U eemptyset,Usubseteq S}(-1)^{|U|-1}g_U ]

    这样就可以枚举子集进行暴力 DP 了,复杂度 (O(2^n))

    而本题中我们可以把 (S) 记录成每种大小的大点分别出现的次数,枚举子集可以改成枚举 (U) 中每种大小的大点出现了多少次后乘一个组合数,这样处理之后复杂度可以通过。

    Code

    #include <bits/stdc++.h>
    #define mp std::make_pair
    
    typedef std::pair<int, int> pii;
    typedef std::vector<pii> vpii;
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    const int N = 45, M = 3e5 + 5, EI = 1e9 + 7;
    
    int n, p[N], ToT, son[M][N], Root, f[M], C[N][N], g[N], h[N][N], fac[N];
    vpii nums[M];
    bool vis[N];
    
    int dfs(int cur, vpii now)
    {
    	int u; nums[u = ++ToT] = now; vpii nxt = now;
    	int lst = now.empty() ? 0 : now[now.size() - 1].first;
    	for (int i = lst; i <= n - cur; i++) if (i)
    	{
    		if (i == lst) nxt[nxt.size() - 1].second++;
    		else nxt.push_back(mp(i, 1));
    		son[u][i] = dfs(cur + i, nxt);
    		nxt = now;
    	}
    	return u;
    }
    
    void dfs_trans(int u, int dep, int v, int in, int out,
    int now, vpii &trans)
    {
    	if (dep == nums[u].size())
    	{
    		if (u == v) return;
    		trans.push_back(mp(1ll * now * h[out][in] % EI, v));
    		return;
    	}
    	int x = nums[u][dep].second, y = nums[u][dep].first;
    	for (int i = 0; i <= x; i++)
    		dfs_trans(u, dep + 1, v, in, out, 1ll * now * C[x][i] % EI, trans),
    			v = son[v][y], in += y, out -= y, now = EI - now;
    }
    
    int dp(int u)
    {
    	if (f[u] != -1) return f[u];
    	if (u == Root) return f[u] = 1;
    	vpii trans; int sze = 0, cnt = 0; f[u] = 0;
    	for (int i = 0; i < nums[u].size(); i++)
    		sze += nums[u][i].first * nums[u][i].second,
    			cnt += nums[u][i].second;
    	dfs_trans(u, 0, Root, 0, sze, cnt & 1 ? 1 : EI - 1, trans);
    	for (int i = 0; i < trans.size(); i++)
    		f[u] = (1ll * trans[i].first * dp(trans[i].second) + f[u]) % EI;
    	return f[u];
    }
    
    int main()
    {
    	read(n); Root = dfs(0, vpii(0));
    	for (int i = 1; i <= n; i++) read(p[i]);
    	std::vector<int> cir;
    	for (int i = 1; i <= n; i++) if (!vis[i])
    	{
    		int c = 0;
    		for (int j = i; !vis[j]; j = p[j]) c++, vis[j] = 1;
    		cir.push_back(c);
    	}
    	int u = 1;
    	std::sort(cir.begin(), cir.end());
    	for (int i = 0; i < cir.size(); i++) u = son[u][cir[i]];
    	for (int i = 0; i <= n; i++) C[i][0] = 1;
    	for (int i = 1; i <= n; i++)
    		for (int j = 1; j <= i; j++)
    			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % EI;
    	memset(f, -1, sizeof(f)); fac[0] = 1;
    	for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % EI;
    	for (int i = 0; i < n; i++)
    	{
    		for (int j = 0; j <= i; j++)
    			g[i] = (1ll * fac[j] * fac[n - j - 1] % EI * C[i][j] + g[i]) % EI;
    		h[0][i] = 1;
    		for (int j = 1; j <= n; j++)
    			h[j][i] = 1ll * h[j - 1][i] * g[i] % EI;
    	}
    	return std::cout << dp(u) << std::endl, 0;
    }
    
  • 相关阅读:
    .NET 动态向Word文档添加数据
    .NET FileUpLoad上传文件
    Jquery 客户端生成验证码
    ASP.NET MVC 5 基本构成
    .NET 发布网站步骤
    Jquery 选择器大全
    .NET 知识整理笔记
    .NET 三层架构
    C#知识整理笔记
    .NET MD5加密解密代码
  • 原文地址:https://www.cnblogs.com/xyz32768/p/14253797.html
Copyright © 2020-2023  润新知