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;
}

浙公网安备 33010602011771号