P9129 [USACO23FEB] Piling Papers G
前置知识
数位dp,区间dp
思路
妙妙题。
看到 求满足一定条件在 \(A,B\le10^{18}\) 之间的数,很显然会想到数位 dp。
所以按照思路,先将问题转化为求 \(1-N\) 之间的数。但是和普通的数位 dp 不同的是,我们在转移时不仅可以在后面加,也可以在前面加。好,我不会了。所以我打开了题解。
我们发现在前面加实际上也是可以实现的,套上一个区间 dp 即可。设 \(dp_{i,j,l,r,0/1/2}\) 表示用了字片 \(i\) 到 \(j\) 拼成数的 \(l\) 到 \(r\) 与目标在这一段的关系大于/等于/小于,转移就变得容易起来了,对于每个新加入的数放在左边或右边即可。然后来思考时间复杂度 \(O(n^2len^2)\) (\(len\) 表示位数 )。完工。
#include <bits/stdc++.h>
using namespace std;
const int M=310,N=20,mod=1e9+7;
#define ll long long
int b[N],wss,n,q,a[M];
ll A,B,f[N][N][3],ans[3][M][M];
void upd(ll &x,ll y){x=(x+y)%mod;}
void cw(ll z)
{
wss=0;
while(z)
{
b[++wss]=z%10;
z/=10;
}
reverse(b+1,b+1+wss);
return ;
}
void dp(int lx)
{
for(int i=n;i>=1;i--)
{
memset(f,0,sizeof(f));
for(int j=i;j<=n;j++)
{
for(int l=1;l<wss;l++)
for(int r=wss;r>l;r--)//注意枚举顺序
{
for(int k=0;k<=2;k++)
{
if(b[l]>a[j])upd(f[l][r][0],f[l+1][r][k]);//当前小,全部小
if(b[l]==a[j])upd(f[l][r][k],f[l+1][r][k]);//当前位相等,大小是后面的大小
if(b[l]<a[j]) upd(f[l][r][2],f[l+1][r][k]);
} //枚举放左边
if(b[r]==a[j])for(int k=0;k<=2;k++)upd(f[l][r][k],f[l][r-1][k]);//当前位相等,大小是前面的大小
if(b[r]<a[j])
{
upd(f[l][r][0],f[l][r-1][0]);//前面小就小
upd(f[l][r][2],(f[l][r-1][1]+f[l][r-1][2])%mod);
}
if(b[r]>a[j])
{
upd(f[l][r][0],(f[l][r-1][0]+f[l][r-1][1])%mod);//前面大就大
upd(f[l][r][2],f[l][r-1][2]);
}
//枚举放右边
}
for(int l=1;l<=wss;l++)
{
if(b[l]>a[j])upd(f[l][l][0],2);//特殊处理,前面加和后面加一样但是两种方案
if(b[l]==a[j])upd(f[l][l][1],2);
if(b[l]<a[j])upd(f[l][l][2],2);
}
//将所有答案先记下来
upd(ans[lx][i][j],(f[1][wss][0]+f[1][wss][1])%mod);//位数相同的贡献
for(int r=1;r<wss;r++)//位数小
for(int k=0;k<=2;k++)upd(ans[lx][i][j],f[1][r][k]);
//cout<<ans[lx][i][j]<<'\n';
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>A>>B;
for(int i=1;i<=n;i++)cin>>a[i];
cw(A-1);
dp(0);//处理A-1
cw(B);
dp(1);
cin>>q;
for(int i=1;i<=q;i++)
{
int l,r;cin>>l>>r;
cout<<(ans[1][l][r]-ans[0][l][r]+mod)%mod<<'\n';
}
return 0;
}

浙公网安备 33010602011771号