簡單基礎數位 dp 題
終於比較理解了數位 \(dp\) ( qwq
處理大數區間的計數,分成每一位考慮,\(f_{pos}\) 考慮從高到低位在第 \(pos\) 位並且滿足某些條件的答案,這個東西我們可以記憶化搜尋,但是注意要設計好狀態,不然會漏或者重複計算某些情況 qwq 。
需要觀察題目有沒有前導 \(0\) 限制,不過這個也不是重點了。
關鍵在於對狀態的設計。
數位 \(dp\) 模板題。
設當前我們要統計的數為 \(G\),\(dp_{pos,sta}\) 表示從高到低第 \(pos\) 位已經選了 \(sta\) 個 \(G\) 的方案數。直接 \(dp\) 即可。
int DFS(int pos,int sta,int lim,int zero) {// lim 表示最高位限制,zero 表示是否在前導零 if(!pos) return sta; if(!lim && !zero && (~dp[pos][sta])) return dp[pos][sta]; int sum=0, up=(lim)?s[pos]:9; for(ri int i=0;i<=up;i++) { int pp=zero&&(!i); sum+=DFS(pos-1,sta+((!pp)&&i==G),lim&&(i==up),zero&&(!i));//如果不在前導零才可以貢獻,否則當統計 0 的貢獻時候會炸掉。 } if(!lim && !zero) dp[pos][sta]=sum; return sum; }
又一個數位 \(dp\) 模板題。
設 \(dp_{pos,pre}\) 為第 \(pos\) 位且上一位的數是 \(pre\) 時的答案,直接 \(dp\) 。注意當處於前導 \(0\) 狀態卻滿足條件時可以直接 \(continue\)。
int DFS(int pos,int pre,int lim,int zero) { if(!pos) return 1; if((!lim) && (!zero) && (~dp[pos][pre+20])) return dp[pos][pre+20]; int sum=0, up=(lim)?s[pos]:9; for(ri int i=0;i<=up;i++) { if(abs(i-pre)<2&&(!zero)) continue; sum+=DFS(pos-1,i,lim&&(i==up),zero&&(!i)); } if(!lim && !zero) dp[pos][pre+20]=sum; return sum; }
比起前兩道要難一些的數位 \(dp\) 題。
考慮一個字串存在至少一個子串是迴文串的情況。顯然,這個子串的最短長度為 \(2\) 或 \(3\),所以我們可以考慮記錄在第 \(pos\) 位時,前兩位分別的值。然後你發現這連樣例都過不去...
考慮一種情況是 \(090\),這種情況你發現 \(pos-2\) 位置上是處於前導 \(0\) 狀態。所以我們還需要記錄處於第 \(pos\) 位時,前兩位是否處於前導 \(0\) 狀態。然後發現這個東西要開 \(6\) 維陣列...
\(dp_{pos,m1,m2,sta,qwq1,qwq2}\) 記錄第 \(pos\) 位時,第 \(pos-1\)
int DFS(int pos,int sta,int m1,int m2,int qwq1,int qwq2,int lim,int zero)
{
if(!pos)
{
/*if(sta)
{
for(ri int i=len;i;i--) printf("%lld",cnt[i]);
puts("");
}*/
return sta;
}
if((!lim) && (!zero) && (~dp[pos][m1][m2][sta][qwq1][qwq2])) return dp[pos][m1][m2][sta][qwq1][qwq2];
int sum=0, up=(lim)?(s[pos]-'0'):9;
for(ri int i=0;i<=up;i++)
{
int qq=(((m1==i)&&(!qwq1))||((m2==i)&&(!qwq2)))||sta;
int pp=(zero&&(!i));
cnt[pos]=i;
sum=(sum+DFS(pos-1,qq,i,m1,pp,qwq1,lim&&(i==up),pp))%Mod;
cnt[pos]=0;
}
if((!lim) && (!zero) ) dp[pos][m1][m2][sta][qwq1][qwq2]=sum;
return sum;
}
帶些思考的數位 \(dp\) 題。
考慮 \(1-9\) 區間的數的 \(lcm\) 最大為 \(2520\),所以你對於算出的那個數 \(x\),只需要考慮 \(x\%2520\) 即可。然後發現要開個差不多 \(20\times 2520 \times 2520\) 的陣列還要用好多次,會 \(MLE\)...
觀察到 \(1-9\) 區間的數的 \(lcm\) 個數僅有 \(48\) 個,可以用類似於離散化一樣的思想預處理一下就好了。
inline void Init()
{
for(ri int a=0;a<=3;a++)
for(ri int b=0;b<=2;b++)
for(ri int c=0;c<=1;c++)
for(ri int d=0;d<=1;d++)
g[++cnt]=ksc(2,a)*ksc(3,b)*ksc(5,c)*ksc(7,d);
sort(g+1,g+1+cnt);
for(ri int i=1;i<=cnt;i++) book[g[i]]=i;
pw[0]=1;
for(ri int i=1;i<=18;i++) pw[i]=pw[i-1]*10;
}
int DFS(int pos,int sta,int now,int lim,int zero)
{
if(!pos)
{
if(sta%g[now]) return 0;
return 1;
}
if((!lim) && (!zero) && (~dp[pos][sta][now])) return dp[pos][sta][now];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++)
{
int pp=(sta+i*pw[pos-1]%Mod)%Mod;
int qq=g[now];
if(i) qq=qq*i/__gcd(i,qq);
qq=book[qq];
sum+=DFS(pos-1,pp,qq,lim&&(i==up),zero&&(!i));
}
if((!lim) && (!zero)) dp[pos][sta][now]=sum;
return sum;
}
原數的值這個狀態顯然存不下...考慮到上一題我們的做法,將這個原數對某個數進行取模。
發現各位數字之和的值域 \(x\) 非常小,設原數為 \(sta\),則我們要得到 \(sta\%x==0\)。所以我們可以列舉各位數字之和 \(x\),把 \(x\) 作為模數即可。
int DFS(int pos,int sta,int now,int Mod,int lim,int zero)
{
if(!pos)
{
if((!sta) && now==Mod) return 1;
return 0;
}
if((!lim) && (!zero) && (~dp[pos][sta][now])) return dp[pos][sta][now];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++) sum+=DFS(pos-1,(sta*10+i)%Mod,now+i,Mod,lim&&(i==up),zero&&(!i));
if((!lim) && (!zero)) dp[pos][sta][now]=sum;
return sum;
}
inline int S(int k)
{
int cnt=0;
while(k) s[++cnt]=k%10, k/=10;
int res=0;
for(ri int i=1;i<=cnt*9;i++)
{
memset(dp,-1,sizeof(dp));
res+=DFS(cnt,0,0,i,1,1);
}
return res;
}
考慮把原數先變為二進位制表示。然後列舉二進位制數的 \(1\) 的個數 \(i\),利用快速冪統計貢獻。
注意在數位 \(dp\) 求方案數時不能對 \(1e7+7\) 取膜,因為它不是質數。
int DFS(int pos,int sta,int now,int lim,int zero)
{
if(!pos) return (now==sta);
if((!lim) && (!zero) && (~dp[pos][sta])) return dp[pos][sta];
int sum=0, up=(lim)?s[pos]:1;
for(ri int i=0;i<=up;i++) sum=(sum+DFS(pos-1,sta+i,now,lim&&(i==up),zero&&(!i)));
if((!lim) && (!zero)) dp[pos][sta]=sum;
return sum;
}
inline int S(int k)
{
int cnt=0;
while(k) s[++cnt]=(k&1ll), k>>=1ll;
int res=1;
for(ri int i=1;i<=cnt;i++)
{
memset(dp,-1,sizeof(dp));
res=res*ksc(i,DFS(cnt,0,i,1,1))%Mod;
}
return res;
}
和之前題目一樣的套路,直接判斷第 \(i\) 位與 \(d\) 的關係即可。
int DFS(int pos,int sta,int lim,int zero)
{
if(!pos) return (!sta);
if((!lim) && (!zero) && (~dp[pos][sta])) return dp[pos][sta];
int sum=0, up=(lim)?(s[pos]-'0'):9;
for(ri int i=0;i<=up;i++)
{
if((len-pos+1)&1ll)
{
if(i==d) continue;
sum=(sum+DFS(pos-1,(sta*10+i)%m,lim&&(i==up),zero&&(!i)))%Mod;
}
else
{
if(i^d) continue;
sum=(sum+DFS(pos-1,(sta*10+i)%m,lim&&(i==up),zero&&(!i)))%Mod;
}
}
if((!lim) && (!zero)) dp[pos][sta]=sum;
return sum;
}