傳送門:http://acm.hdu.edu.cn/showproblem.php?pid=6644
比賽的時候看到這題以爲是水題,然後發現有q組詢問。。。。數位DP預處理之後一次詢問應該是O(n)的吧,除非倍增去求logn?
完了這怎麼倍增啊,每個位置可以選9個數字,都不是線性的怎麼倍增臥槽,然後這題最後竟然過了64個。。。是我太菜
賽後看claris的題解發現果然是倍增,但是實在看不懂輕重鏈剖分的那個選擇最大方案數的點是什麼意思,網上也沒有搜到別人寫得題解。。。於是對着代碼抄了一遍。。。邊抄邊理解了1個多小時。。。
從前往後的問號填數字,我們可以想象成每個?有9個子節點,那麼所有的填樹情況從前往後就形成了一棵深度爲問號個數的父節點在左子節點在右樹,每個節點表示從1到這個位置id填數情況,也可以都可以知道當前1-id所代表的數字 %m的餘數res。
我們知道最後要整除m,那麼所有葉子節點哪些是合法的,不合法的就要刪去,於是此時我們就知道了每個節點的子樹大小,那麼由於是要字典序第k小,那麼每個點選擇兒子節點的順序都是從0到9的,我從前向後枚舉每一位選哪個數字,如果該兒子i子樹大小sum<k,那麼說明選擇i走下去走不到第k小,那麼k-sum,繼續枚舉k+1。這樣一次詢問就是O(n*10)的複雜度,但是我們這裏有1e5組詢問,所以要另想辦法了。
我們使用輕重剖分的思想,每個點 i 的最大的兒子節點爲 mxson[i],那麼我們每次只要判斷k 和 ss=sum[0]+sum[1]...sum[maxson[i]-1]的數值和k比哪個大,如果ss<k 且 ss+sum[maxson[i]]>=k,那麼答案就在走mxson[i]這個節點中,如果ss>=k,那麼就在0--maxson[i]-1中枚舉,若ss+sum[maxson[i]]<k,那就去maxson[i]+1...9中取枚舉。
那我們把這樣的最大兒子節點倍增,倍增的路線就是一直沿着最大兒子走,那麼這條路線就是重鏈,如果不滿足上述條件,就去枚舉走哪條輕鏈。我們知道樹鏈剖分走輕重鏈從頭走到尾是logn次,而由於在重鏈上還要邊跑邊判斷還能不能繼續,所以一條重鏈上的走法也是logn。那麼一次詢問就是log^2n。
倍增的數組處理也挺細節的(要我自己寫我肯定寫不出)。我抄的這份claris的代碼中,f[i][j]表示1到i-1位餘 j 的方案數,g[j][k]表示上一位位置餘j,這一位放 k,會餘多少,go[k][i][j]表示從第i位餘j的情況下走1<<k步會餘多少,val[k][i][j]表示走1<<k步數字%mod會餘的值,st[k][i][j]表示i位餘j的情況下走1<<k步前面全沿着重鏈走,最後一步不走重鏈的方案數,en[k][i][j]就是全走重鏈的情況下的方案數,can[i][j]表示第i位能不能放j這個數字。
#include<bits/stdc++.h>
#define maxl 50010
using namespace std;
const long long inf=1e18;
const int mod=1e9+7;
int n,m,q,up=17;
int mi[maxl];
int g[21][21];
int go[20][maxl][21],val[20][maxl][21];
long long f[maxl][21];
long long st[20][maxl][21],en[20][maxl][21];
bool can[maxl][10];
char s[maxl];
inline long long fix(long long x)
{
return x<inf?x:inf;
}
inline void prework()
{
scanf("%d%d%d",&n,&m,&q);
scanf("%s",s+1);
for(int i=0;i<m;i++)
for(int j=0;j<10;j++)
g[i][j]=(i*10+j)%m;
for(int i=1;i<=n;i++)
if(s[i]=='?')
for(int j=0;j<10;j++)
can[i][j]=true;
else
{
for(int j=0;j<10;j++)
can[i][j]=false;
can[i][s[i]-'0']=true;
}
f[n+1][0]=1;
for(int j=1;j<m;j++)
f[n+1][j]=0;
long long tmp,sz,now,sum;int nxt;
for(int i=n;i>=1;i--)
for(int j=0;j<m;j++)
{
tmp=0;sz=-1;nxt=-1;
for(int k=0;k<10;k++)
if(can[i][k])
{
now=f[i+1][g[j][k]];
tmp=fix(tmp+now);
if(now>sz) nxt=k,sz=now;
}
f[i][j]=tmp;
go[0][i][j]=g[j][nxt];
val[0][i][j]=nxt;
sum=0;
for(int k=0;k<nxt;k++)
if(can[i][k])
sum=fix(sum+f[i+1][g[j][k]]);
st[0][i][j]=sum;
en[0][i][j]=fix(sum+f[i+1][g[j][nxt]]);
}
for(int k=1;k<up;k++)
for(int i=1;i+(1<<k)<=n+1;i++)
for(int j=0;j<m;j++)
{
int x=go[k-1][i][j],len=1<<(k-1);
go[k][i][j]=go[k-1][i+len][x];
val[k][i][j]=(1ll*val[k-1][i][j]*mi[len]+val[k-1][i+len][x])%mod;
st[k][i][j]=fix(st[k-1][i][j]+st[k-1][i+len][x]);
en[k][i][j]=fix(st[k-1][i][j]+en[k-1][i+len][x]);
}
}
inline int query(long long k)
{
if(k>f[1][0]) return -1;
int id=1,res=0,ret=0;
long long tmp;
while(id<=n)
{
for(int i=up-1;i>=0;i--)
if(id+(1<<i)<=n+1 && st[i][id][res]<k && k<=en[i][id][res])
{
ret=(1ll*ret*mi[1<<i]+val[i][id][res])%mod;
k-=st[i][id][res];
res=go[i][id][res];
id+=1<<i;
}
if(id>n) break;
for(int i=0;i<10;i++)
if(can[id][i])
{
tmp=f[id+1][g[res][i]];
if(k>tmp)
k-=tmp;
else
{
ret=(10LL*ret+i)%mod;
id++;
res=g[res][i];
break;
}
}
}
return ret;
}
inline void mainwork()
{
long long x;
for(int i=1;i<=q;i++)
{
scanf("%lld",&x);
printf("%d\n",query(x));
}
}
int main()
{
mi[0]=1;
for(int i=1;i<maxl;i++)
mi[i]=1ll*10*mi[i-1]%mod;
int t;
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
prework();
mainwork();
// print();
}
return 0;
}