Subsequences Galore(容斥+高维前缀和)

G. Subsequences Galore

[Link](Problem - G - Codeforces)

题意

​ 给定一个字符串序列 [ t 1 , t 2 , . . . , t m ] [t_1,t_2,...,t_m] [t1,t2,...,tm],定义 f ( [ t 1 , t 2 , . . . , t m ] ) f([t_1,t_2,...,t_m]) f([t1,t2,...,tm])为至少是其中一个字符串 t i t_i ti的子序列的字符串个数,其中 f ( [ ] ) = 0 f([])=0 f([])=0

给定 m m m个字符串序列 对 [ s 1 , s 2 , . . . , s m ] [s_1,s_2,...,s_m] [s1,s2,...,sm]每一个子集 [ s i 1 , s i 2 , . . . , s i k ] [s_{i1},s_{i2},...,s_{ik}] [si1,si2,...,sik]求出 f ( [ s i 1 , s i 2 , . . . , s i k ] ) f([s_{i1},s_{i2},...,s_{ik}]) f([si1,si2,...,sik]) 998 , 244 , 353 998,244,353 998,244,353 取模后的值。

输出 f ( [ s i 1 , s i 2 , . . . , s i k ] ) × k × ( i 1 + i 2 + . . . + i k ) f([s_{i1},s_{i2},...,s_{ik}])\times k \times (i_1+i_2+...+i_k) f([si1,si2,...,sik])×k×(i1+i2+...+ik)异或和(不取模)。

注意每个字符串 s i s_i si中的字母都是排好序的。

思路

f ( [ s i 1 , s i 2 , . . . , s i k ] ) × k × ( i 1 + i 2 + . . . + i k ) f([s_{i1},s_{i2},...,s_{ik}])\times k \times (i_1+i_2+...+i_k) f([si1,si2,...,sik])×k×(i1+i2+...+ik)这个式子除了 f f f以外都很好求出来,因此先看 f f f怎么求。

​ 要算字符序列的不同子集的贡献,我们先任取一个子集 [ s i 1 , s i 2 , . . . , s i k ] [s_{i1},s_{i2},...,s_{ik}] [si1,si2,...,sik]来看 f ( [ s i 1 , s i 2 , . . . , s i k ] ) f([s_{i1},s_{i2},...,s_{ik}]) f([si1,si2,...,sik])怎么算,根据定义至少是其中一个字符串 t i t_i ti的子序列,因此我们可以容斥一下 r e s = 所 有 是 其 中 一 个 字 符 串 子 串 的 − 所 有 是 其 中 两 个 字 符 串 字 串 的 + 所 有 是 三 个 字 符 串 子 串 的 + . . . res= 所有是其中一个字符串子串的-所有是其中两个字符串字串的+所有是三个字符串子串的+... res=++...。发现所有含有奇数个字符串的系数为 + + +,含有偶数个字符串的系数为 − -

​ 这个东西不是很好算,我们先看怎么算恰好是某几个字符串的子串的数量,例如 s i 1 , s i 2 , s i 3 s_{i1},s_{i2},s_{i_3} si1,si2,si3,因为要是他们公共的子串,所以对于每个字符不能超过他们里面最小的数量,对于每个字符 a ∼ z a\sim z az,假设有 c n t cnt cnt个就有 c n t + 1 cnt+1 cnt+1中选法,根据乘法原理乘起来即可,因此可以先预处理出每个字符串每个字母的数量,暴力枚举 1 < < n 1<<n 1<<n种情况,找到这些选取的字符串里每个字符的最小数量即可。

现在我们知道怎么算恰好是某些字符串的公共子串了,但是怎么将他的子集的贡献加到它上面呢(这些字符串某些字符的公共子串的数量)。

这里引入针对于子集求和问题的高维前缀和:

回顾二维前缀和的求法: s [ i ] [ j ] + = s [ i − 1 ] [ j ] + s [ i ] [ j − 1 ] − s [ i − 1 ] [ j − 1 ] ; s[i][j] += s[i-1][j] + s[i][j - 1] - s[i - 1][j - 1]; s[i][j]+=s[i1][j]+s[i][j1]s[i1][j1];这是基于容斥求出来的,其实我们还可这样求

	for (int i = 1; i <= n; i ++ )
 		for (int j = 1; j <= m; j ++ )	
 		    s[i][j] += s[i][j - 1];
    for (int i = 1; i <= n; i ++ )
 		for (int j = 1; j <= m; j ++ )	
 		    s[i][j] += s[i - 1][j];

即分别计算每一维的贡献,也可以补充不漏的算出。

回到这道题,如果我们按照维度给他分类最多有 23 23 23个数我们开一个 23 23 23维的数组肯定不显示,我们可以状态压缩,开一个 1 < < 23 1<<23 1<<23大小的数组,其中每一位为 1 1 1代表选这个集合 0 0 0表不选。

因为要容斥我们设计前缀和数组:

f [ i ] [ 0 ] : 状 态 为 i 的 集 合 且 子 集 含 有 偶 数 个 字 符 串 的 f 函 数 值 的 和 f[i][0]:状态为i的集合且子集含有偶数个字符串的f函数值的和 f[i][0]:if

f [ i ] [ 1 ] : 状 态 为 i 的 集 合 且 子 集 含 有 奇 数 个 字 符 串 的 f 函 数 值 的 和 f[i][1]:状态为i的集合且子集含有奇数个字符串的f函数值的和 f[i][1]:if

求出恰好某些字符串的公共子串之后,对于转移求高维前缀和的操作

for (int i = 0; i < n; i ++)
    	for (int j = 0; j < 1 << n; j ++)
            	if (i >> j & 1) // 当前集合选了第i个字符,那么它就可以从不选第i个的其他相同的前一维压加过来
            	f[j][0] += f[j - (1 << i)][0], f[j][1] += f[j - (1 << i)][1]; 

我们得到的每个集合的 f [ i ] [ 0 ] , f [ i ] [ 1 ] f[i][0],f[i][1] f[i][0],f[i][1]之后暴力枚举所有的集合,算 k 和 ( i 1 × i 2 × . . . × i k ) k和(i_1\times i_2\times ...\times i_k) k(i1×i2×...×ik)即可。

Code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <queue>
#include <vector>
#include <map>
#include <bitset>
#include <unordered_map>
#include <cmath> 
#include <stack>
#include <iomanip>
#include <deque> 
#include <sstream>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 998244353;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
#define tpyeinput int
inline char nc() {static char buf[1000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
inline void read(tpyeinput &sum) {char ch=nc();sum=0;while(!(ch>='0'&&ch<='9')) ch=nc();while(ch>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+(ch-48),ch=nc();}
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
	e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[24][26], c[26];
char str[200011];
LL f[10000001][2];

int main() {
	cin >> n;
	for (int i = 1; i <= n; i ++) {
		scanf("%s", str + 1);
		int m = strlen(str + 1);
		for (int j = 1; j <= m; j ++)
			++a[i][str[j] - 'a'];
	}
	
	for (int i = 1; i < 1 << n; i ++) {
		memset(c, 0x3f, sizeof c);
		int cnt = 0;
		for (int j = 0; j < n; j ++)
			if ((i >> j) & 1)  {
				++ cnt;
				for (int k = 0; k < 26; k ++)
					c[k] = min(c[k], a[j + 1][k]);
			}
		LL res = 1;
		for (int k = 0; k < 26; k ++) res = res * (c[k] + 1) % mod;
		if (cnt & 1) f[i][1] = res;
		else 		 f[i][0] = res;
	}
	
	
	for (int j = 0; j < n; j ++)	
		for (int i = 0; i < 1 << n; i ++)
			if((i >> j) & 1)	
				f[i][0] += f[i - (1 << j)][0], f[i][0] %= mod,
				f[i][1] += f[i - (1 << j)][1], f[i][1] %= mod;
	
	LL res = 0;
	for (int i =  1; i < 1 << n; i ++)	{
		int cnt = 0, num = 0;
		for (int j = 0; j < n; j ++)
			if (i >> j & 1) {
				cnt ++;
				num += j + 1;
			}
		f[i][1] -= f[i][0];
		if (f[i][1] < 0) f[i][1] += mod;
		res ^= cnt * num * f[i][1];
	}
	
	cout << res << endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值