zoukankan      html  css  js  c++  java
  • UOJ#193. 【UR #14】人类补完计划(容斥原理+状压dp)

    http://uoj.ac/problem/193

    题解:

    考虑先求出(f[S])表示(S)这个点集的基环树个数。

    可以先求(f_{cur}[S])表示(S)点集成环的方案数:

    (dp[S][i])表示已选(S)中的点,最后一个是(i),的方案数,每次只能新加一个比(S)最小大的点,这样每个(ge 3)环会被恰好记(2)次。

    然后考虑类来DAG计数的方法,每次想扩展所有的叶子,可以枚举叶子的子集容斥。

    (f[S]),枚举(T∩S=∅),转移给(f[S|T]),乘上(T)连到(S)的方案数,和容斥系数((-1)^{|T|+1})

    搞出了(f)之后看怎么求答案:

    同样枚举叶子的子集容斥,
    枚举两个集合(S,T),系数就是(f[S]*(T到S的方案方案数)),容斥系数(2^{|S|}*(-1)^{|T|}),通过二项式逆展开,发现会恰好算要算的东西。

    (T)(S)的方案数,可以(2^n*n^2)预处理。

    所以复杂度:(O(3^n)),还可以子集卷积优化,不过很复杂。

    Code:

    #include<bits/stdc++.h>
    #define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
    #define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
    #define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
    #define ll long long
    #define pp printf
    #define hh pp("
    ")
    using namespace std;
    
    const int mo = 998244353;
    
    ll ksm(ll x, ll y) {
    	ll s = 1;
    	for(; y; y /= 2, x = x * x % mo)
    		if(y & 1) s = s * x % mo;
    	return s;
    }
    
    const ll inv2 = ksm(2, mo - 2);
    
    const int N = 17;
    
    int n, m, x, y;
    int a[N][N];
    
    int a2[N];
    
    void add(ll &x, ll y) { (x += y) %= mo;}
    
    ll xs1[1 << 16];
    
    #define low(x) ((x) & -(x))
    
    int c1[1 << 16];
    int lg2[1 << 16];
    
    namespace sub1 {
    	ll f[1 << 16][N];
    	int w[1 << 16];
    	void dp() {
    		ff(i, 1, a2[n]) c1[i] = c1[i ^ low(i)] + 1;
    		ff(i, 1, n) lg2[1 << i] = 1;
    		ff(i, 1, a2[n]) lg2[i] += lg2[i - 1];
    		
    		ff(s, 1, a2[n])	{
    			ff(j, 0, n) if(s >> j & 1) {
    				w[s] = j + 1; break;
    			}
    			if(c1[s] == 1) {
    				f[s][w[s]] = 1;
    			}
    		}
    		ff(s, 1, a2[n]) {
    			fo(i, 1, n) if(f[s][i]) {
    				fo(j, 1, n) if(!(s >> (j - 1) & 1) && a[i][j] && j > w[s]) {
    					int ns = s | a2[j - 1];
    					add(f[ns][j], f[s][i]);
    				}
    			}
    		}
    		ff(s, 1, a2[n]) {
    			int cnt = 0;
    			ff(i, 0, n) cnt += s >> i & 1;
    			if(cnt <= 2) continue;
    			fo(i, 1, n) if(f[s][i] && a[i][w[s]]) {
    				ll v = f[s][i];
    				v = v * inv2 % mo;
    				add(xs1[s], v);
    			}
    		}
    	}
    }
    
    ll ans = 0;
    
    namespace sub2 {
    	ll f[1 << 16];
    	ll g[1 << 16];
    	ll c[1 << 16][N];
    	
    	void dp() {
    		ff(s, 1, a2[n]) {
    			f[s] = xs1[s];
    			fo(k, 1, n) {
    				fo(j, 1, n) if(s >> (j - 1) & 1)
    					c[s][k] += a[k][j];
    			}
    		}
    		ff(i, 1, a2[n]) {
    			g[0] = 1;
    			for(int j = (i + 1) | i; j < a2[n]; j = (j + 1) | i) {
    				int k = i ^ j;
    				g[k] = g[k ^ low(k)] * c[i][lg2[low(k)] + 1] % mo;
    				f[j] = (f[j] + f[i] * g[k] % mo * (c1[k] % 2 ? 1 : -1)) % mo;
    			}
    		}
    		ans = 0;
    		ff(i, 1, a2[n]) if(f[i]) {
    			g[0] = 1;
    			ans = (ans + f[i] * a2[c1[i]]) % mo;
    			for(int j = (i + 1) | i; j < a2[n]; j = (j + 1) | i) {
    				int k = i ^ j;
    				g[k] = g[k ^ low(k)] * c[i][lg2[low(k)] + 1] % mo;
    				ans = (ans + f[i] * g[k] % mo * a2[c1[i]] * (c1[k] % 2 ? -1 : 1)) % mo;
    			}
    		}
    		ans = (ans % mo + mo) % mo;
    		pp("%lld
    ", ans);
    	}
    }
    
    int main() {
    	a2[0] = 1; fo(i, 1, 16) a2[i] = a2[i - 1] * 2;
    	scanf("%d %d", &n, &m);
    	fo(i, 1, m) {
    		scanf("%d %d", &x, &y);
    		a[x][y] = a[y][x] = 1;
    	}
    	sub1 :: dp();
    	sub2 :: dp();
    }
    
  • 相关阅读:
    [转]三五个人十来条枪 如何走出软件作坊成为开发正规军
    [转]asp.net页面字体变大问题总结
    [练手7]传值和传引用区别
    [练手3]选择排序
    [练手5]希尔排序
    oracle用select for update 中断后无法操作表的处理:杀掉SESSION
    [转]最省时管理法:让你一天随意
    [练手4]插入排序
    没有对“C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files”的写访问权限
    NUnit单元测试编写指南
  • 原文地址:https://www.cnblogs.com/coldchair/p/13405921.html
Copyright © 2011-2022 走看看