• Codeforces 1326F Wise Men (容斥原理、状压 DP、子集和变换、划分数)


    题目链接

    F1: https://codeforces.com/contest/1326/problem/F1
    F2: https://codeforces.com/contest/1326/problem/F2

    题解

    好题。
    考虑容斥,对每个 01 串求满足串中为 (1) 的位置必须为 (1)、串中为 (0) 的位置 (0)(1) 均可的排列的个数。最后把超集和还原回来即可。
    这样的好处是,本质不同的状态只有拆分数 (P(n)) 个,即一个状态的答案只和所有连续的 (1) 的长度构成的可重集合有关。于是考虑 DFS 枚举划分数。
    先预处理 (f_S) 表示 (S) 点集内有多少条哈密尔顿回路经过的边全是 (1).
    对于一个状态,假设每一段的长度是 (a_1,a_2,...,a_l). 那么就相当于我们要找 (l) 个状态 (s_1,s_2,...,s_l), 满足 (forall i, ext{bitcnt}(s_i)=a_i)( ext{or}^l_{i=1}s_i=2^n-1),贡献为 (prod^l_{i=1}f_{s_i}). 写成集合幂级数的形式,设 (g_i) 是一个集合幂级数,满足有且仅有在 ( ext{bitcnt}(s)=i) 的位置有值,值为 (f_s),则这个状态的总方案数等于 (g_{a_1},g_{a_2},...,g_{a_l}) 的子集卷积。于是可以直接使用子集卷积计算,可以做到 (O(2^nP(n)n^2)) 左右的复杂度。但是还是不行。
    注意到 (sum^l_{i=1}a_i=n),且 (g_{a_i}) 仅仅在 ( ext{bitcnt}(a_i)) 处有值。也就是说我们其实根本不需要使用子集卷积——子集卷积的方法是给每个集合幂级数增加一维长度的限制,但是这里我们已经对长度进行了限制!假设有任何两个 (s_i) 有交,那么所有 (s_i) 的并的大小就不可能为 (n). 于是直接对 (g_{a_i}) 这些集合幂级数作 or 卷积即可。
    枚举划分后,计算 or 卷积的时间复杂度为所有划分方案的总长度,足以通过。但是我们可以边 DFS 边维护 or 卷积,复杂度变成了搜索树的节点个数乘以 (2^n).
    划分数搜索时,比较好的方法是从大到小搜索,每次放的数不超过上次放的。这时只要上次放的数不小于 (2),每个节点分叉数就一定大于 (1). 假设剩下一堆 (1) 是一起放的,那么节点数显然为 (O(P(n)));否则据 EI 爷说是 (O(P(n)sqrt n)) 的。这里可以预处理 (g_1) 的幂来做到前者的复杂度。
    总时间复杂度 (O(2^n(n^2+T(n))),其中 (T(n)=O(P(n)))(O(P(n)sqrt n))(O( ext{sum of length of all partitions})).

    代码

    #include<bits/stdc++.h>
    #define llong long long
    #define mkpr make_pair
    #define x first
    #define y second
    #define iter iterator
    #define riter reversed_iterator
    #define y1 Lorem_ipsum_dolor
    using namespace std;
    
    inline int read()
    {
    	int x = 0,f = 1; char ch = getchar();
    	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
    	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
    	return x*f;
    }
    
    const int mxN = 18;
    int bitcnt[(1<<mxN)+3];
    llong f[mxN+3][(1<<mxN)+3];
    llong dp[(1<<mxN)+3][mxN+3];
    llong g[mxN+3][(1<<mxN)+3];
    int part[mxN+3],aux[mxN+3];
    llong h[(1<<mxN)+3];
    char a[mxN+3][mxN+3];
    int n,m;
    
    void dfs(int rst,int lst)
    {
    	if(rst==0)
    	{
    		llong ret = 0ll;
    		for(int i=0; i<(1<<n); i++)
    		{
    			ret += ((n-bitcnt[i])&1)?-g[m][i]:g[m][i];
    		}
    //		printf("("); for(int i=1; i<=m; i++) printf("%d ",part[i]); printf("): %I64d
    ",ret);
    		for(int i=1; i<=m; i++) {aux[i] = part[m-i+1];}
    		do
    		{
    			int pos = 0,sta = 0;
    			for(int i=1; i<=m; i++)
    			{
    				for(int j=1; j<aux[i]; j++,pos++) {sta|=(1<<pos);}
    				pos++;
    			}
    //			printf("sta=%d
    ",sta);
    			h[sta] += ret;
    		} while(next_permutation(aux+1,aux+m+1));
    		return;
    	}
    	for(int i=1; i<=rst&&i<=lst; i++)
    	{
    		part[++m] = i;
    		for(int j=0; j<(1<<n); j++) {g[m][j] = g[m-1][j]*f[i][j];}
    		dfs(rst-i,i);
    		m--;
    	}
    }
    
    int main()
    {
    	for(int i=1; i<(1<<mxN); i++) bitcnt[i] = bitcnt[i>>1]+(i&1);
    	n = read(); for(int i=0; i<n; i++) {scanf("%s",a[i]); for(int j=0; j<n; j++) a[i][j] -= 48;}
    	for(int i=0; i<n; i++) dp[1<<i][i] = 1ll;
    	for(int i=1; i<(1<<n); i++) for(int j=0; j<n; j++) if(i&(1<<j))
    	{
    		llong x = dp[i][j];
    		for(int k=0; k<n; k++) if(a[j][k]&&!(i&(1<<k)))
    		{
    			dp[i|(1<<k)][k] += x;
    		}
    	}
    	for(int i=0; i<(1<<n); i++) for(int j=0; j<n; j++) if(i&(1<<j))
    	{
    		f[bitcnt[i]][i] += dp[i][j];
    	}
    //	for(int i=0; i<(1<<n); i++) printf("%I64d ",f[bitcnt[i]][i]); puts("");
    	for(int i=0; i<n; i++) for(int j=0; j<(1<<n); j++) if(j&(1<<i))
    	{
    		for(int k=0; k<n; k++) {f[k][j] += f[k][j^(1<<i)];}
    	}
    	for(int i=0; i<(1<<n); i++) g[0][i] = 1ll; dfs(n,n);
    	for(int i=0; i<n-1; i++) for(int j=0; j<(1<<n-1); j++) if(j&(1<<i))
    	{
    		h[j^(1<<i)] -= h[j];
    	}
    	for(int i=0; i<(1<<n-1); i++) printf("%I64d ",h[i]); puts("");
    	return 0;
    }
    
  • 相关阅读:
    zsh(yum装包的时候,有时候会不行)
    an error occurred during the file system check错误的解决
    Linux使用tcpdump命令抓包保存pcap文件wireshark分析
    安装win7或win8系统时UEFI和Legacy模式的设置
    查看LINUX当前负载
    svn update 每更新一项就输出一行信息,使用首字符来报告执行的动作 这些字符的含义是:
    svn分支管理进行迭代开发
    Linux命令行下创建纳入版本控制下的新目录
    svn update -r m path 代码还原到某个版本(这样之前的log日志也就没了,也就是清空log日志)
    二进制日志BINARY LOG清理
  • 原文地址:https://www.cnblogs.com/suncongbo/p/12792845.html
Copyright © 2020-2023  润新知