zoukankan      html  css  js  c++  java
  • CF1400G

    1400G - Mercenaries

    考场上想到枚举,但是只想到了 (2 ^ m) 枚举矛盾,然后用 NOI Online 2 游戏 类似的容斥掉,结果式子推着推着就复杂度爆了 wtcl。

    (Update:后来看其它大佬的博客是可以的,下面的内容是抄的neal的题解)

    如果我们把一个矛盾关系 ((a, b)) ,在图论意义上连一条边,每个人看作一个点。

    这个问题变成了一个独立集计数(还要满足人数限制的)问题。

    显然,每个连通图的方案是独立的。

    由于有人数限制,我们可以想到枚举人数 (x),算 (x) 人的独立集个数,我们可以把每个连通图看作一个整体,剩下的看作一个整体。我们把每个连通图的人数看作体积,价值为方案数,把所有连通图做一遍分组背包,然后剩余的点用组合数就好,然后算出剩下的人数(用简单的差分预处理 + 简单的组合数)。

    那么对于一个连通图 (i) 和人数 (j),设 (h_{i, j, k}) 为总人数恰好为 (j) 人,从 (i) 连通图选出 (k) 个相互不冲突的人数的方案数。那么怎么预处理出这个东西呢?

    关注到此题的突破口是 (m le 20),那么最大的连通图最多有 (m + 1 le 21) 个点。那么对于每个 (i, j),我们可以先把满足人数限制的点集 (S_{i, j}) 找出来,那么就是从 (S) 中选 (k) 个互不冲突,我们可以再设 (g_{i, S, k}) 表示从 (i) 连通图的 (S) 点集中选 (k) 个的方案,这样 (h_{i, j, k} = g_{i, S_{i, j}, k})。怎么算 (g)?对于有 (k) 个点的连通图,我们可以 (O(2 ^ k(k + m))) 枚举选了的点集,判断这个连通图是否没有矛盾,赋值 (g_{i, S, |S|} = 1),然后最外层循环一位 0 位置在 (p)(g_{i, S or 2^p, k} gets g_{i, S, k}),这样就可以 (O(2^kk^2)) 线性递推了。(注意必须最外层循环,这个的话,可以理解为认为规定了“补0”的顺序,这样方案才不会重复,否则比如00,00->01->11; 00 -> 10 -> 11 会产生重复方案)

    设联通块有 (s) 个,第 (i) 个联通块大小是 (s_i)。总时间复杂度 (O(displaystyle sum_{i=1}^s (s_i^2 + m) 2 ^{s_i} + s_in(sum_{j=1}^{i-1}s_j)))

    前者最大是 (2 ^ {21} imes 401 approx 8 imes 10 ^ 8),后者最大大概是 (3 imes 10^5 imes 40^2 approx 5 imes 10^8)。时间限制是 (7s) 所以貌似可以过,并且很多状态似乎无用可以减掉,总之就过了,玄学。。

    #include <cstdio>
    #include <iostream>
    #include <vector>
    using namespace std;
    
    typedef long long LL;
    
    const int N = 300005, P = 998244353, S = 21;
    
    int n, m, L[N], R[N], a[N], b[N], g[1 << S][S + 1], c[N], d[N], e[N];
    int fact[N], infact[N], f[N][S * 2], tot, fa[N], sz[N], Log[1 << S], now[N], ans;
    
    bool vis[N], ins[N];
    vector<int> col[N];
    
    int inline power(int a, int b) {
    	int res = 1;
    	while (b) {
    		if (b & 1) res = (LL)res * a % P;
    		a = (LL)a * a % P;
    		b >>= 1;
    	}
    	return res;
    }
    
    int inline C(int a, int b) {
    	if (a < b || a < 0 || b < 0) return 0;
    	return (LL)fact[a] * infact[b] % P * infact[a - b] % P;
    }
    
    int find(int x) {
    	return x == fa[x] ? x : fa[x] = find(fa[x]);
    }
    
    int main() {
    	scanf("%d%d", &n, &m); 
    
    	fact[0] = infact[0] = 1;
    	for (int i = 1; i <= n; i++)
    		fact[i] = (LL)fact[i - 1] * i % P;
    	infact[n] = power(fact[n], P - 2);
    	for (int i = n - 1; i; i--)
    		infact[i] = (LL)infact[i + 1] * (i + 1) % P;
    
    	for (int i = 0; i <= 20; i++) Log[1 << i] = i;
    	for (int i = 1; i <= n; i++) {
    		scanf("%d%d", L + i, R + i);
    		f[i][0] = 1, fa[i] = i, sz[i] = 1;
    	}
    	for (int i = 1; i <= m; i++) {
    		scanf("%d%d", a + i, b + i);
    		fa[find(a[i])] = find(b[i]);
    		ins[a[i]] = ins[b[i]] = true;
    	}
    	for (int i = 1; i <= n; i++) {
    		if (!ins[i]) c[L[i]]++, c[R[i] + 1]--;
    	}
    	for (int i = 1; i <= n; i++) col[find(i)].push_back(i), c[i] += c[i - 1];
    	for (int t = 1; t <= n; t++) {
    		if (find(t) != t || col[t].size() == 1) continue;
    		int k = col[t].size();
    		for (int i = 1; i < (1 << k); i++)
    			for (int j = 0; j <= k; j++) g[i][j] = 0;
    		for (int i = 1; i < (1 << k); i++) {
    			bool ok = true; int cnt = 0;
    			for (int j = 0; j < k; j++) 
    				if (i >> j & 1) vis[col[t][j]] = true, cnt++;
    			for (int j = 1; j <= m; j++)
    				if (vis[a[j]] && vis[b[j]]) { ok = false; break; }
    			for (int j = 0; j < k; j++) 
    				if (i >> j & 1) vis[col[t][j]] = false;
    			if (ok) g[i][cnt] = 1;
    		}
    		for (int v = 0; v < k; v++) {
    			for (int i = 1; i + 1 < (1 << k); i++) {
    				for (int j = 1; j <= k; j++) {
    					if (!g[i][j] || i >> v & 1) continue;
    					(g[i + (1 << v)][j] += g[i][j]) %= P;
    				}
    			}
    		}
    		for (int i = 1; i <= n; i++) d[i] = 0, e[i] = 0;
    		for (int i = 0; i < k; i++) {
    			d[L[col[t][i]]] += 1 << i, d[R[col[t][i]] + 1] -= 1 << i;
    			e[L[col[t][i]]] ++, e[R[col[t][i]] + 1]--;
    		}
    		for (int i = 1; i <= n; i++) {
    			d[i] += d[i - 1], e[i] += e[i - 1];
    			now[i] += e[i];
    			for (int j = now[i]; j; j--) {
    				for (int v = 1; v <= min(j, e[i]); v++) {
    					if (!f[i][j - v] || !g[d[i]][v]) continue;
    					f[i][j] = (f[i][j] + (LL)f[i][j - v] * g[d[i]][v]) % P;
    				}
    			}
    		}
    	}
    	for (int i = 1; i <= n; i++) {
    		for (int j = 0; j <= now[i]; j++) {
    			ans = (ans + (LL)f[i][j] * C(c[i], i - j)) % P;
    		}
    	}
    	printf("%d
    ", ans);
    	return 0;
    }
    
  • 相关阅读:
    控制台输出带颜色的文字
    三次登入冻结操作
    python-装饰器&生成器&迭代器
    python-常用内置函数
    Falsk框架 Session 与 Flask-Session
    Flask框架 请求与响应 & 模板语法
    数据分析 之 NumPy
    Selenium浏览器自动化测试工具
    requests模块 高级应用
    数据解析
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13565410.html
Copyright © 2011-2022 走看看