[SDOI2009]Bill的挑战

该博客探讨了SDOI2009比赛中的一道题目,涉及动态规划和组合数学的应用。作者指出,考虑数据范围后,问题可以通过状态压缩来解决。内容包括如何使用容斥原理来计算与特定数量字符串匹配的串的数量,并通过递推公式进行求解。在计算过程中,博主经历了思考和调试,最终简化了代码并成功解决问题,但对此表示了自己的不满意,认为代码风格不佳。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

某送门

看数据范围是一种优良的传统,本题 N ≤ 15 N\leq 15 N15,状压无疑了QAQ。

然后我们看到:刚好与 K K K 个串匹配的个数,就想到了容斥。

感觉自己思维好跳跃啊不过似乎也是正常思路?

由于是与 K K K 个串匹配,不能单纯的“奇加偶减”,我们用 f [ i ] f[i] f[i] 表示恰好与 K K K 个串匹配的串的个数, C n t [ i ] Cnt[i] Cnt[i] 表示与至少 K K K 个串匹配的串的个数。

画几个韦恩图(不过挺难画的),可以发现对于每个表示与至少 K K K 个串匹配的个数的“圆圈”都有交集,所以我们依次减去这些交集:

f [ i ] = C n t [ i ] − ∑ i + 1 N C [ j ] [ i ] ∗ f [ j ] f[i]=Cnt[i]-\sum\limits^N_{i+1} C[j][i] * f[j] f[i]=Cnt[i]i+1NC[j][i]f[j]

对于 C n t Cnt Cnt 数组的计算,要根据每个字符串的集合算出它们的交集,然后根据这些集合表示的字符串的个数来判断应该把它加到哪个 C n t Cnt Cnt 中,这个过程很简单,详见代码(话说这个部分我开始以为自己的思路有问题,又是debug又是在草稿纸上画来画去,最后看了题解一怒之下删了 t m ^{tm} tm 一大段代码直接 A A A 了。(枯了)目前最优解 r k 2 rk2 rk2

多测不清零==爆零n行泪

码风贼丑,见谅。

C o d e : Code: Code:

#include <cstdio>
#include <cstring>
#define MOD 1000003
#define MAXS 1 << 16
#define lowbit(x) (x & ~x + 1)
#define max(x, y) (x > y ? x : y)

char S[16][51], Union[MAXS][51];
int Pow[51] = {1}, Log[MAXS], bitlen[MAXS], C[16][16], f[16], Cnt[20];
bool vis[MAXS];

int main() {
	for (int i(1); i <= 50; ++ i) Pow[i] = Pow[i - 1] * 26 % MOD;
	for (int i(0); i < 16; ++ i) Log[1 << i] = i;
	for (register int i(1); i < MAXS; ++ i) bitlen[i] = bitlen[i ^ lowbit(i)] + 1;
	for (int i(0); i <= 15; ++ i) {
		C[i][0] = 1;
		for (int j(1); j <= i; ++ j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
	}
	int T;
	scanf("%d", &T);
	while (T --) {
		int N, K, len;
		scanf("%d%d", &N, &K);
		memset(vis, 0, sizeof(vis)), memset(Cnt, 0, sizeof(Cnt));
		for (register int i(0); i < N; ++ i) scanf("%s", S[i]);
		len = strlen(S[0]);
		memset(Union, '\0', sizeof(Union));
		for (register int i(0); i < 50; ++ i) Union[0][i] = '?';
		
		for (register int i(1); i < 1 << N; ++ i) {
			if (!vis[i ^ lowbit(i)]) strcpy(Union[i], Union[i ^ lowbit(i)]);
			else {vis[i] = true; continue;}
			
			register char * s(S[Log[lowbit(i)]]), * Uni(Union[i]);
			for (register int j(0); j < len; ++ j)
			if (Uni[j] != '?' && s[j] != '?' && Uni[j] != s[j]) {vis[i] = true; break;}
			else if (s[j] != '?') Uni[j] = s[j];
		}
		
		for (register int i(1); i < 1 << N; ++ i) if (!vis[i] && bitlen[i] >= K) {
			int cnt(0);
			for (register int j(0); j < len; ++ j) if (Union[i][j] == '?') ++ cnt;
			(Cnt[bitlen[i]] += Pow[cnt]) %= MOD;
		}
		
		for (int i(N); i >= K; -- i) {
			f[i] = Cnt[i];
			int sum(0);
			for (int j(i + 1); j <= N; ++ j)
			sum += C[j][i] * f[j], sum = (sum + MOD) % MOD;
			f[i] = ((f[i] - sum) % MOD + MOD) % MOD;
		}
	 	printf("%d\n", f[K]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值