Topcoder SRM655-Div1-Lv2 Nine

涉及知识点:DP

题意

给出一串长度为 \(m\ (\leq5000)\) 的序列 \(d\)\(0<d_i<2^5\),我们称一个 \(m\) 位数 \(a\)\(k\) 下是合格的当且仅当:抽取 \(a\) 的第 \(i\) 位(从左到右)组成一个新数 \(b\),抽取的 \(i\) 满足 \(d_i\) 二进制下的第 \(k\) 位(从右到左)为 \(1\),使得 \(b\) 可以被 \(9\) 整除。现在需要你求出有多少种 \(m\) 位数满足在 \(1\leq k\leq n\ (n\leq 5)\) 下都合格。

例如:\(d=1,2,3=(01)_2,(10)_2,(11)_2\),那么对于一个三位数 \(789\),它在 \(k=1\) 下为 \(89\)\(k=2\) 下为 \(79\)

DP做法

六维大 DP。。。

总思路就是分开考虑每种 \(d_i\) 对应的 \(a_i\) 该怎么填。

f[sta][a][b][c][d][e]sta 表示当前处理的 \(d_i\)a b c d e 分别表示 \(k=1\sim5\)\(b\)\(9\) 的余数,那么答案就是 f[2^5-1][0][0][0][0][0]

g[i][j] 表示 \(i\) 个一位数的和加起来模 \(9\)\(j\) 的方案数。

那么,如何转移呢?发现对于 \(d_i\) 相同的 \(i\),影响对应的 \(k\) 都是一样的,可以先枚举他们产生的总贡献(模 \(9\) 意义下),再具体计算总贡献分配到哪个 \(i\) 产生的方案数。

具体看代码,有注释:

#include<bits/stdc++.h>
#define add(x,y) x=((x)+(y))%P;
using namespace std;
typedef long long LL;
const int MAXM=5005,MAXN=6,MAXB=1<<MAXN;
const LL P=1e9+7;
int n,m,d[MAXM],cnt[MAXB],full;
LL f[MAXB][10][10][10][10][10],g[MAXM][10];
inline LL qpow(LL a,LL b){
    a%=P;
    LL res=1;
    while(b){
        if(b&1) res=res*a%P;
        a=a*a%P;
        b>>=1;
    }
    return res;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>d[i];
        cnt[d[i]]++;
    }

    g[0][0]=1;
    for(int i=1;i<=m;i++){
        for(int j=0;j<9;j++){
            for(int k=0;k<=9;k++){
                add(g[i][j],g[i-1][(j-k%9+9)%9]);//计算知道总贡献j,分配到i个位置上的方案数
            }
        }
    }

    full=1<<n;
    f[0][0][0][0][0][0]=qpow(10,cnt[0]);//d=0的位置不影响,怎么填都可以
    for(int sta=1;sta<full;sta++){//枚举每种d
        int cur[5],nxt[5];
        for(cur[0]=0;cur[0]<(n>=1?9:1);cur[0]++)//枚举k=1~5时余数的状态
        for(cur[1]=0;cur[1]<(n>=2?9:1);cur[1]++)
        for(cur[2]=0;cur[2]<(n>=3?9:1);cur[2]++)
        for(cur[3]=0;cur[3]<(n>=4?9:1);cur[3]++)
        for(cur[4]=0;cur[4]<(n>=5?9:1);cur[4]++)
            for(int i=0;i<9;i++){//枚举总贡献
                memcpy(nxt,cur,sizeof(cur));
                for(int j=0;j<n;j++){
                    if(sta&(1<<j))
                        nxt[j]=(nxt[j]-i+9)%9;//贡献只对d对应的k生效,准确的说这里应该是pre而不是nxt
                }
                add(f[sta][cur[0]][cur[1]][cur[2]][cur[3]][cur[4]],f[sta-1][nxt[0]][nxt[1]][nxt[2]][nxt[3]][nxt[4]]*g[cnt[sta]][i]%P);
            }
    }
    cout<<f[full-1][0][0][0][0][0]<<endl;
    return 0;
}

记忆化搜索做法

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
typedef long long LL;
const LL P=1e9+7;
const int MAXN=5005,pow9[]={1,9,81,729,6561,59049,531441};
int n,m;
LL f[9*9*9*9*9+5][35],g[MAXN][10];
vector<pii>d;
map<int,int>cnt;
LL calc(int t,int m){//calc相当于DP中的g,计算知道总贡献m,分配到t个位置上的方案数
    if(g[t][m]!=-1) return g[t][m];
    if(t==0){
        if(m==0) return g[t][m]=1;
        else return g[t][m]=0;
    }
    else{
        int res=0;
        for(int i=0;i<=9;i++){
            res=(res+calc(t-1,(m-i+9)%9))%P;
        }
        return g[t][m]=res;
    }
}
LL func(int sta,int ptr){//sta为上文提到的abcde的状压写法,即k=1~5时的余数,ptr为当前枚举到的d
    if(f[sta][ptr]!=-1) return f[sta][ptr];
    if(ptr==m){
        if(sta==0) return f[sta][ptr]=1;//合法
        else return f[sta][ptr]=0;//不合法
    }
    else{
        LL res=0;
        for(int i=0;i<9;i++){
            int nxtsta=0;
            for(int j=0;j<n;j++){
                int cur=(sta/pow9[j])%9;
                if(d[ptr].first&(1<<j)) cur=(cur+i)%9;//贡献只对d对应的k生效
                nxtsta+=cur*pow9[j];
            }
            res=(res+func(nxtsta,ptr+1)*calc(d[ptr].second,i)%P)%P;
        }
        return f[sta][ptr]=res;
    }
}
int main(){
    cin>>n>>m;
    for(int i=1,x;i<=m;i++){
        cin>>x;
        cnt[x]++;
    }
    for(auto it:cnt){
        d.push_back(it);
        // cout<<it.first<<' '<<it.second<<endl;
    }
    m=d.size();
    memset(f,-1,sizeof(f));
    memset(g,-1,sizeof(g));
    cout<<func(0,0)<<endl;
    return 0;
}
posted @ 2024-05-15 20:01  MessageBoxA  阅读(29)  评论(0)    收藏  举报