zoukankan      html  css  js  c++  java
  • [CF1326F] Wise Men

    这本来是一道容斥好题,但是被 * 一样的子任务分配毁了……

    (其实是因为一万个人过了 F1 但是我没过导致上分失败(x

    倒着分析一下,设 (f_s) 表示状态 (s) 下的方案数,其中 (s_i=1) 表示第 (i) 个人与第 (i+1) 个人之间一定有边,(s_i=0) 表示他们之间可以有边,也可以没有边(注意 (|s|=n-1))。

    那么通过 (f_s),我们可以容斥得到真实的答案。具体地说,只需要将高维前缀和变为高维后缀差分即可,即枚举 (k),每次枚举时 (f_x := f_x-f_{x+2^k}),当且仅当 (x operatorname{and} 2^k=0)。正确性可以通过数学归纳法证明:(|s|=1) 时显然正确;(|s|=m) 时,设 (|s|=m-1) 时正确,则枚举 (kin[0, m-2]) 后,每个 (xoperatorname{and} 2^{m-1} e 0)(f_x) 已经变成真实值, 且剩余的 (f_x) 中不含 (2^{m-1}) 的部分已经容斥完毕。发现剩余的 (f_x) 中,包含 (2^{m-1}) 的部分恰好都被 (f_{x+2^{m-1}}) 算到了,并且每一项的符号都恰与 (f_{x+2^{m-1}}) 对应的项相反,因此直接减去 (f_{x+2^{m-1}}) 就是对的。

    现在的问题是怎么求 (f_s)。对于一个 (s) 中极长的一段连续的 (1),设其长度为 (m),则它代表一条包含了 (m+1) 个人的简单路径,其中相邻的人之间都有边。由于我们不关心连续段之间是否有边,每一个连续段都是独立的,它们的顺序也无关紧要。因此只需要对每一个 (sum a_i=n) 的可重集合 (a),求出它对应的答案即可。搜一下发现这样的 (a) 最多只有 (p_n=385) 个。

    现在我们的任务是,给定一个 (sum a_i =n) 的可重集合,将所有人分成 (|a|) 组,满足第 (i) 组人数为 (a_i),且一种分配方法的权值为 (prod b_i),其中 (b_i) 表示第 (i) 组的人的哈密顿路径数量。

    考虑预处理 (g_{k, s}) 表示将 (s_i=1) 的人分配到同一组中,且组里恰好有 (k) 个人(即 (s) 中恰好有 (k) 个位置是 (1),否则 (g_{k, s}=0))的哈密顿路径数量,可以通过简单的状压 dp 完成。

    如果对于每一个 (a_i),都暴力枚举不为 (0)(g_{a_i, s}) 进行状压 dp,复杂度大约是 (inom{frac{n}{2}}{n}^2) 级别,无法接受。

    但是注意到我们要求的是给每个人分配恰好一个组的方案数。由于 (sum a_i=n),所以如果某人被分配了多于一个组,则必然有人没有被分配到组。因此它的方案数等价于给每个人分配至少一个组的方案数。设 (h_{k, s}) 表示只在 (s_i=1) 的人中找到一条长度为 (k) 的哈密顿路径的方案数,显然 (h_{k, s}=sum_{tsubseteq s} g_{k, t})。设 (d_s=prod h_{a_i, s}),则只需要容斥就可以得到 (a) 对应的答案。此处容斥类似 [ZJOI2016] 小星星。

    如果暴力枚举 (a),再暴力枚举 (a_i) 计算,复杂度为 (2^nsum |a|),但是如果在搜索 (a) 的过程中,即时算出每层对应的 (d),即可优化到 (2^n p_n),因为搜索树上分叉的数量等于叶节点数量。前面求 (h_{k, s}) 时需要对每个 (k) 做一遍高维前缀和(子集和),因此复杂度是 (Oleft(2^n(p_n+n^2) ight))

    Code:

    #include <bits/stdc++.h>
    #define R register
    #define mp make_pair
    #define ll long long
    #define pii pair<int, int>
    using namespace std;
    const int mod = 998244353, N = (1 << 18) + 100;
    
    int n, g[20][20];
    ll f[N][20], h[20][N], tmp[20][N], ans[N];
    char s[20];
    vector<int> a;
    
    inline int addMod(int a, int b) {
    	return (a += b) >= mod ? a - mod : a;
    }
    
    inline ll quickpow(ll base, ll pw) {
    	ll ret = 1;
    	while (pw) {
    		if (pw & 1) ret = ret * base % mod;
    		base = base * base % mod, pw >>= 1;
    	}
    	return ret;
    }
    
    template <class T>
    inline void read(T &x) {
    	x = 0;
    	char ch = getchar(), w = 0;
    	while (!isdigit(ch)) w = (ch == '-'), ch = getchar();
    	while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    	x = w ? -x : x;
    	return;
    }
    
    void dfs(int res, int lst) {
    	if (!res) {
    		ll s = 0;
    		int st = a.size();
    		for (R int i = 0; i < 1 << n; ++i)
    			s += (_popcnt32(i) ^ n) & 1 ? -tmp[st][i] : tmp[st][i];
    		vector<int> b = a;
    		reverse(b.begin(), b.end());
    		do {
    			int tl = 0, t = 0;
    			for (auto &k : b) {
    				for (R int i = 1; i < k; ++i)
    					t ^= 1 << tl, ++tl;
    				++tl;
    			}
    			ans[t] = s;
    		} while (next_permutation(b.begin(), b.end()));
    		return;
    	}
    	for (R int i = 1; i <= min(lst, res); ++i) {
    		a.push_back(i);
    		for (R int j = 0; j < 1 << n; ++j)
    			tmp[a.size()][j] = tmp[a.size() - 1][j] * h[i][j];
    		dfs(res - i, i), a.pop_back();
    	}
    	return;
    }
    
    int main() {
    	read(n);
    	for (R int i = 0; i < n; ++i) {
    		scanf("%s", s);
    		for (R int j = 0; j < n; ++j)
    			g[i][j] = s[j] - '0';
    	}
    	h[0][0] = 1;
    	for (R int i = 0; i < n; ++i)
    		f[1 << i][i] = 1, ++h[1][1 << i];
    	for (R int i = 1; i < 1 << n; ++i) {
    		int s = _popcnt32(i);
    		if (s == 1) continue;
    		for (R int j = 0; j < n; ++j)
    			if (i & (1 << j)) {
    				for (R int k = 0; k < n; ++k)
    					if (k != j && (i & (1 << k)) && g[j][k])
    						f[i][j] += f[i ^ (1 << j)][k];
    				h[s][i] += f[i][j];
    			}
    	}
    	for (R int i = 0; i < n; ++i)
    		for (R int j = 0; j < n; ++j)
    			for (R int k = 0; k < 1 << n; ++k)
    				if (k & (1 << j))
    					h[i][k] += h[i][k ^ (1 << j)];
    	memcpy(tmp[0], h[0], sizeof (h[0]));
    	dfs(n, n);
    	for (R int i = 0; i < n; ++i)
    		for (R int j = 0; j < 1 << n >> 1; ++j)
    			if (j & (1 << i))
    				ans[j ^ (1 << i)] -= ans[j];
    	for (R int i = 0; i < 1 << n >> 1; ++i)
    		printf("%lld ", ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    软件架构自学笔记-- 软件架构师是如何工作的
    软件架构自学笔记---架构概述
    HDFS Shell命令操作与java代码操作
    定义maven的项目结构
    企业级Spring应用的搭建
    HDFS与java API应用
    Azure Database for MySQL 报 Please specify SSL options and retry.
    seata-server安装、运行
    sentinel-dashboard安装、运行(ubuntu)
    nacos-server集群 安装、运行(ubuntu)
  • 原文地址:https://www.cnblogs.com/suwakow/p/12620453.html
Copyright © 2011-2022 走看看