洛谷 P2167 [SDOI2009] Bill的挑战

P2167 [SDOI2009] Bill的挑战

洛谷 P2167 [SDOI2009] Bill的挑战_第1张图片

约定: N ≤ 15 , ∣ S i ∣ ≤ 50 N \leq 15, |S_i| \leq 50 N15,Si50

思路

我们不能直接枚举状态 S ∈ [ 0 , 2 15 − 1 ] S \in [0,2^{15} - 1] S[0,2151] 来表示与这些字符串匹配的有多少个 T T T,因为可能出了状态里面选中的字符串 S S S,那些没被选中的也被匹配了。例如: S 1 = a ? , S 2 = ? a , T = a a S_1 = a?,S_2 = ?a,T = aa S1=a?,S2=?a,T=aa,如果我们只要和 S 1 S_1 S1 匹配, T T T 是符合的,但是同时 T T T 也和 S 2 S_2 S2 匹配了。

我们考虑枚举每一位选择的字母 ′ a ′ → ′ z ′ 'a' \rightarrow 'z' az,如果这样选,匹配状态会怎样更新。那么我们就需要之前的匹配状态,定义: d p [ i ] [ s ] dp[i][s] dp[i][s] 为处理到第 i i i 个位置,且匹配状态为 s s s 时的方案数。那么最终答案就是: ∑ d p [ l e n ] [ S ] \sum dp[len][S] dp[len][S],其中 S S S 的二进制 1 1 1 的个数恰好为 k k k

初始化 d p [ 0 ] [ 2 n − 1 ] = 1 dp[0][2^{n} -1] = 1 dp[0][2n1]=1,还没开始匹配时所有的字符串都匹配。

转移:对于当前选择的字符 c c c,设它在位置 i i i 的匹配状态为: m a t c h [ i ] [ c ] match[i][c] match[i][c] (用二进制来看),这个信息我们预处理出来。
那么转移就是: d p [ i ] [ S ] → d p [ i + 1 ] [ S dp[i][S] \rightarrow dp[i + 1][S dp[i][S]dp[i+1][S & m a t c h [ i ] [ c ] ] match[i][c]] match[i][c]]

时间复杂度: O ( l e n × 2 N × 26 ) O(len \times 2^{N} \times 26) O(len×2N×26)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

template<class T>
constexpr T power(T a, ll b){
    T res = 1;
    while(b){
        if(b&1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出
    ll res = a * b - ll(1.L * a * b / mod) * mod;
    res %= mod;
    if(res < 0) res += mod; //误差
    return res;
}

template<ll P>
struct MLL{
    ll x;
    constexpr MLL() = default;
    constexpr MLL(ll x) : x(norm(x % getMod())) {}

    static ll Mod;
    constexpr static ll getMod(){
       if(P > 0) return P;
       return Mod;
    }

    constexpr static void setMod(int _Mod){
       Mod = _Mod;
    }
    constexpr ll norm(ll x) const{
       if(x < 0){
           x += getMod();
       }
       if(x >= getMod()){
           x -= getMod();
       }
       return x;
    }
    constexpr ll val() const{
       return x;
    }
    explicit constexpr operator ll() const{ 
       return x; //将结构体显示转换为ll类型: ll res = static_cast(OBJ)
    }
    constexpr MLL operator -() const{ //负号,等价于加上Mod
       MLL res;
       res.x = norm(getMod() - x);
       return res;
    }
    constexpr MLL inv() const{
       assert(x != 0);
       return power(*this, getMod() - 2); //用费马小定理求逆
    }
    constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象
       x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用
       return *this;
    }
    constexpr MLL& operator += (MLL rhs) & {
       x = norm(x + rhs.x);
       return *this;
    }
    constexpr MLL& operator -= (MLL rhs) & {
       x = norm(x - rhs.x);
       return *this;
    }
    constexpr MLL& operator /= (MLL rhs) & {
       return *this *= rhs.inv();
    }
    friend constexpr MLL operator * (MLL lhs, MLL rhs){
       MLL res = lhs;
       res *= rhs;
       return res;
    }
    friend constexpr MLL operator + (MLL lhs, MLL rhs){
       MLL res = lhs;
       res += rhs;
       return res;
    }
    friend constexpr MLL operator - (MLL lhs, MLL rhs){
       MLL res = lhs;
       res -= rhs;
       return res;
    }
    friend constexpr MLL operator / (MLL lhs, MLL rhs){
       MLL res = lhs;
       res /= rhs;
       return res;
    }
    friend constexpr std::istream& operator >> (std::istream& is, MLL& a){
       ll v;
       is >> v;
       a = MLL(v);
       return is;
    }
    friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){
       return os << a.val();
    }
    friend constexpr bool operator == (MLL lhs, MLL rhs){
       return lhs.val() == rhs.val();
    }
    friend constexpr bool operator != (MLL lhs, MLL rhs){
       return lhs.val() != rhs.val();
    }
};

const ll mod = 1000003;
using Z = MLL<mod>;

std::string s[20];

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin >> t;
    while(t--){
        int n, k;
        std::cin >> n >> k;
        fore(i, 0, n) std::cin >> s[i];
        int len = s[0].size();
        std::vector<std::vector<int>> match(len + 1, std::vector<int>(27, 0));
        fore(p, 0, len)
            fore(i, 0, n)
                for(char c = 'a'; c <= 'z'; ++c)
                    if(s[i][p] == c || s[i][p] == '?')
                        match[p][c - 'a'] |= (1 << i);
        
        std::vector<std::vector<Z>> dp(len + 1, std::vector<Z>(1 << n | 1, 0));
        dp[0][(1 << n) - 1] = 1;
        fore(i, 0, len)
            fore(S, 0, 1 << n)
                if(dp[i][S].x)
                    for(char c = 'a'; c <= 'z'; ++c)
                        dp[i + 1][S & match[i][c - 'a']] += dp[i][S];

        Z ans = 0;
        fore(S, 0, 1 << n)
            if(__builtin_popcount(S) == k) //恰好与k个字符串匹配
                ans += dp[len][S];
        std::cout << ans << endl;

    }
    return 0;
}

你可能感兴趣的:(算法,c++,动态规划)