2019 HNCPC D Modulo Nine 数位DP
题意
给定长度n和m个限制(l,r),每一个限制的意义是\(a_l \times a_{l+1} \times ... a_r = 0 \quad mod\ 9\)。
问有多少个满足所有条件且长度为n的10进制数(可以包含前导0)
解题思路
看到可以包含前导零的时候就想到了数位DP,但是比赛的时候没想出怎么记录状态来记忆化搜索。赛后听ljn讲了之后秒懂,我还是太菜了。
模9为0可以分为3种情况:区间内9的个数大于等于1,0的个数大于等于1,或,3的个数+6的个数大于等于2,所以可以用4维数组\(dp_{pos,last09,last36,second36}\)来记录状态
\(dp_{pos,last09,last36,second36}\):pos表示当前枚举到了第几位,last09表示前一个0或9的下标,last36表示前一个3或6的下标,second36表示last36往前一个3或6的下标。
然后按当前位为0或9,3或6,其他数字分类讨论,如果满足以pos为右端点的所有限制就继续转移。
代码实现
和叉姐的标称对拍没有出错,应该是对的吧
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int mod=1e9+7;
int n,m;
int dp[55][55][55][55];//pos 09 36-1 36-2
vector<int>limit[55];
ll dfs(int pos,int last_09,int last_36,int second_36){
if(pos==n+1)return 1;
if(dp[pos][last_09][last_36][second_36]!=-1)return dp[pos][last_09][last_36][second_36];
ll res=0;
for(int i=0;i<=9;i++){//枚举当前位
if(i==0 || i==9){
res=(res+dfs(pos+1,pos,last_36,second_36))%mod;
}
else if(i==3 || i==6){
bool flag=true;
for(int l:limit[pos]){
if(last_09>=l)continue;
if(last_36>=l)continue;
flag=false;
}
if(flag)res=(res+dfs(pos+1,last_09,pos,last_36))%mod;
}
else{
bool flag=true;
for(int l:limit[pos]){
if(last_09>=l)continue;
if(second_36>=l)continue;
flag=false;
}
if(flag)res=(res+dfs(pos+1,last_09,last_36,second_36))%mod;
}
}
dp[pos][last_09][last_36][second_36]=res;
return res;
}
ll f(){
memset(dp,-1,sizeof(dp));
return dfs(1,-1,-1,-1);
}
int main()
{
while(~scanf("%d %d",&n,&m)){
int l,r;
for(int i=1;i<=n;i++)limit[i].clear();
for(int i=1;i<=m;i++){
scanf("%d %d",&l,&r);
limit[r].push_back(l);
}
printf("%lld\n",f());
}
return 0;
}