AtCoder Beginner Contest 246 F - typewriter(容斥原理)

本文介绍了一道AtCoder比赛中的题目F-typewriter,主要涉及容斥原理的应用。通过状压枚举和位运算求解字符串的交集,计算不同子集的贡献值。文章提到了__builtin_popcount()函数在计算二进制位1的个数时的高效性,并展示了完整的C++代码实现。

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

F - typewriter

https://atcoder.jp/contests/abc246/tasks/abc246_f

首先我们根据容斥原理公式:(摘自百度百科):
∣ A 1 ∪ A 2 ∪ . . . ∪ A m ∣ = |A_1\cup A_2\cup...\cup A_m|= A1A2...Am=
∑ 1 ≤ i ≤ m ∣ A i ∣ − ∑ 1 ≤ i ≤ j ≤ m ∣ A i ∩ A j ∣ + ∑ 1 ≤ i ≤ j ≤ k ≤ m ∣ A i ∩ A j ∩ A k ∣ − . . . + ( − 1 ) m − 1 ∣ A 1 ∩ A 2 ∩ . . . ∩ A m ∣ \sum\limits_{1\leq i\leq m}|A_i|-\sum\limits_{1\leq i\leq j\leq m}|A_i\cap A_j|+\sum\limits_{1\leq i\leq j\leq k\leq m}|A_i\cap A_j\cap A_k|-...+(-1)^{m-1}|A_1\cap A_2\cap...\cap A_m| 1imAi1ijmAiAj+1ijkmAiAjAk...+(1)m1A1A2...Am

那么对于这题来说我们可以状压枚举 2 N − 1 2^N-1 2N1 个状态,这 2 N − 1 2^N-1 2N1 个状态其实就一一对应了上述公式中某一个交集。那么这个状态对答案具体贡献的值是多少呢?首先根据这个状态中 1 1 1 的个数可用判断其正负号,然后我们就要求其对应交集中元素个数,几个字符串的交集的元素个数就是他们共有的字母在 L L L 的长度上随便放产生的串的个数,也就是 ( 共 有 的 字 母 ) L (共有的字母)^L ()L 。那么共有的字符有多少呢?我们把字符串状压后求 按位交 即可。

这里求非负 i n t int int 型的二进制个数可用函数__builtin_popcount(),第二次见到这个函数,起初觉得太长,不好记,不如手写,后面发现这个函数底层实现的效率真的高,比手写的要好很多,复杂度几乎是常数而非 l o g log log 的。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;
const int mod = 998244353;

int n, L;
char s[N];
int v[30];

int ksm(int base, int n) {
    int ans = 1;
    while(n) {
        if (n & 1) ans = 1ll * ans * base % mod;
        base = 1ll * base * base % mod;
        n >>= 1;
    }
    return ans;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    scanf("%d%d", &n, &L);
    for (int i = 0; i < n; ++i) {
        scanf("%s", (s + 1));
        int len = strlen(s + 1);
        for (int j = 1; j <= len; ++j) {
            v[i] |= (1 << (s[j] - 'a'));
        }
    }
    ll ans = 0;
    for (int i = 1; i < (1 << n); ++i) {
        int cur = (1 << 26) - 1;
        for (int j = 0; j < n; ++j) {
            if (i & (1 << j)) cur &= v[j];
        }
        if (__builtin_popcount(i) & 1) {
            ans = (ans + ksm(__builtin_popcount(cur), L)) % mod;
        }
        else {
            ans = (ans - ksm(__builtin_popcount(cur), L) + mod) % mod;
        }
    }
    printf("%d", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值