题意
F-Modulo Nine
给定m个区间[li,ri],问带前导零的n位数,有多少个数满足:
∀
i
,
∏
k
=
l
i
r
i
a
k
%
9
=
=
0
\forall i,\prod_{k=l_i}^{r_i}a_k \%9==0
∀i,k=li∏riak%9==0
其中a[k]表示这个数的第k位,n,m<=1000.
分析
思维方向有很多,比较靠谱的是逐位填数dp。
一个区间要合法,只需要有2个3的因子即可,一个0、9算两个3的因子,3、6算一个。我们先不考虑填0、9,考虑只填3、6。那么一个位置的状态只有两种,3的因子或者非3的因子。
我们只用记录最后两个3的因子填在哪里。因为假如一个区间有超过两个因子,前面的可以忽略掉。
设f[i][j]表示最后两个因子出现在i,j上(i<j),其中右端点在j之前的区间已经全部合法,的方案数。
这样的记录的好处是,当f[j][k]要从f[i][j]转移过来的时候,我们知道右端点在j之前的区间已经全部合法了,而j~k-1的区间还未判断,而且他们要合法的最后机会就是i足够大,使得区间包含i,而j一定包含。
那么
f
[
j
]
[
k
]
=
∑
i
f
[
i
]
[
j
]
∗
6
k
−
j
−
1
∗
2
∗
[
右
端
点
在
j
至
k
−
1
的
区
间
的
左
端
点
都
小
于
等
于
i
]
f[j][k]=\sum_i f[i][j]*6^{k-j-1}*2*[右端点在j至k-1的区间的左端点都小于等于i]
f[j][k]=i∑f[i][j]∗6k−j−1∗2∗[右端点在j至k−1的区间的左端点都小于等于i]
系数中,6的幂指j+1~k-1的位置可以填非3因子的数,2指的是k可以填3、6。
注意到限制条件中,左端点都<=i,就是最大值<=i。那么对于每个右端点相同的区间,我们保留左端点最大的那个即可,记Le[x]表示右段点在x的最大的左端点下标。
现在的还有0\9没有考虑。事实上,当我们得到了f[j][k],我们可以直接用相同的方法转移到f[k][k],只是系数变成了1,因为之前填3\6时,这个位置算了两种方案,0\9刚好也是两个。
初始状态就是f[0][0]=1,最后的答案可以用所有f[j][k]中符合要求的状态求得。
这样暴力转移是O(n^3)的,注意到转移中合法的i一定是连续的,拿个前缀和优化一下转移就行了。
代码
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int N=1e3+5,mo=1e9+7,M=1e7+5;
ll f[N][N],six[N],coef,ans,g[N][N];
int Le[N],Lmx,n,m,l,r,i,j,k;
ll get(int l,int r,int j)
{
if (!l) return g[r][j];
else return g[r][j]-g[l-1][j];
}
int main()
{
freopen("F.in","r",stdin);
//freopen("I.out","w",stdout);
n=1e3;
six[0]=1;
fo(i,1,n) six[i]=six[i-1]*6ll%mo;
while (scanf("%d %d",&n,&m)!=EOF)
{
ans=0;
fo(i,1,n) Le[i]=0;
fo(i,0,n) fo(j,0,n) g[i][j]=f[i][j]=0;
fo(i,1,m)
{
scanf("%d %d",&l,&r);
Le[r]=max(Le[r],l);
}
f[0][0]=1;
g[0][0]=1;
fo(k,1,n)
{
Lmx=0;
fd(j,k-1,0)
{
// f[j][k]: f[Lmx[j~k-1]~j][j]
Lmx=max(Lmx,Le[j]);
if (Lmx>j) break;
f[j][k]=get(Lmx,j,j)*six[k-j-1]*2%mo;
}
g[0][k]=f[0][k];
fo(j,1,k-1) g[j][k]=(g[j-1][k]+f[j][k])%mo;
f[k][k]=g[k-1][k];
g[k][k]=(g[k-1][k]+f[k][k])%mo;
}
Lmx=0;
fo(i,1,n) Lmx=max(Lmx,Le[i]);
fo(i,0,n)
if (Lmx<=i)
fo(j,i,n)
{
ans=(ans+f[i][j]*six[n-j])%mo;
}
printf("%lld\n",(ans+mo)%mo);
}
}