zoukankan      html  css  js  c++  java
  • hihoCoder #1646 : Rikka with String II(容斥原理)

    题意

    给你 (n)(01)(S) ,其中有些位置可能为 (?) 表示能任意填 (0/1) 。问对于所有填法,把所有串插入到 (Trie) 的节点数之和(空串看做根节点)。

    (n le 20, 1 le |S_i| le 50)

    题解

    直接算显然不太好算的。

    (Trie) 的节点数其实就是本质不同的前缀个数,可以看做 (n) 个串的所有前缀的并集的大小。

    我们可以套用容斥原理最初的式子。

    [left| igcup_{i=1}^n A_i ight| = sum_{k = 1}^{n} (-1)^{k - 1} sum_{1 le i_1 < i_2 < cdots < i_k le n} |A_{i_1} cap A_{i_2} cap cdots cap A_{i_k}| ]

    这样的话,我们就可以转化成对于每个子集的交集了,也就是公共前缀的个数。

    我们设 (f(S))(S) 集合内的所有子串对于 所有填的方案 的公共前缀的个数。

    那么答案为 (ans = sum_{S subseteq T} (-1)^{|S| - 1} f(S))

    如何得到呢?由于 (n) 很小我们可以暴力枚举集合,然后枚举当前前缀的长度,直接计数。

    1. 如果当前所有的都是 (?) 那么意味着可以任意填 (0/1)
    2. 如果存在一种数字,其他都是 (?) ,那么意味着只能填这种数字。
    3. 如果存在两种数字,那么之后都不可能为公共前缀了,直接退出即可。

    直接实现是 (O(2^n n |S|)) 的。可以把状态集合合并一下优化到 (O(2^n |S|))(但是我太懒了)

    代码

    实现的时候不要忘记是所有填的方案。

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << (x) << endl
    
    using namespace std;
    
    template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
    template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
    
    inline int read() {
    	int x(0), sgn(1); char ch(getchar());
    	for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    	for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    	return x * sgn;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("1646.in", "r", stdin);
    	freopen ("1646.out", "w", stdout);
    #endif
    }
    
    const int N = 21, L = 51, Mod = 998244353;
    
    int n, len[1 << N], Pow[N * L];
    
    char str[N][L];
    
    int main () {
    
    	File();
    
    	n = read();
    
    	Set(len, 0x3f);
    
    	int tot = 0;
    
    	Rep (i, n) {
    		scanf ("%s", str[i] + 1); 
    		len[1 << i] = strlen(str[i] + 1);
    		For (j, 1, strlen(str[i] + 1))
    			if (str[i][j] == '?') ++ tot;
    	}
    	Pow[0] = 1;
    	For (i, 1, tot)
    		Pow[i] = 2ll * Pow[i - 1] % Mod;
    
    	Rep (i, 1 << n)
    		chkmin(len[i], min(len[i ^ (i & -i)], len[i & -i]));
    
    	int ans = 0;
    	Rep (i, 1 << n) if (i) {
    		int res = 0, sum = tot, pre = 0;
    		For (j, 1, len[i]) {
    			int flag = 0, now = 0;
    			Rep (k, n) if (i >> k & 1) {
    				if (str[k][j] == '?') ++ now;
    				else flag |= (str[k][j] - '0' + 1);
    			}
    			sum -= now;
    			if (flag == 3) break;
    			if (!flag) ++ pre;
    			res = (res + Pow[pre + sum]) % Mod;
    		}
    		ans = (ans + (__builtin_popcount(i) & 1 ? 1 : -1) * res) % Mod;
    	}
    	ans += Pow[tot]; if (ans < 0) ans += Mod;
    	printf ("%d
    ", ans);
    
    	return 0;
    
    }
    
  • 相关阅读:
    【嵌入式linux】(第三步):安装串口终端 (ubuntu安装minicom串口终端)
    vim常用命令总结
    usb调试
    查找当前文件夹内容
    Android SDK开发包国内下载地址
    ubuntu下svn使用指南
    Ubuntu vim显示行号语法高亮自动缩进
    android_handler(一)
    HDU 1241 Oil Deposits (DFS)
    计划
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10241690.html
Copyright © 2011-2022 走看看