2021.5.21讲题日
Program in C++14
据说某位神犇说过:数位DP就是抄板子的日常
对不对先不说但是要是谁有板子给我一个,数位DP确实有套路
T1:\(P2606\)
模板题不讲
如果需要,这里有神犇 \(\mathtt{zrt}\) 的视频。私信我领取,从 \(\mathtt{1:01:30}\) 开看
#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
#include<chrono>
#include<random>
#include<unordered_map>
#define ll long long
#define ull unsigned long long
#define rll register long long
#define ri register int
using namespace std;
const int N=100;
ll a,b;
ll ten[N],f[N];
ll ca[N],cb[N];
inline ll read(){
ll x=0,y=1;
char c=getchar();
while (c<'0'||c>'9'){
if(c=='-')
y=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
return x*y;
}
void work(ll x,ll *c){
ll num[20];
memset(num,0,sizeof(0));
int len=0;
while(x){
num[++len]=x%10;
x=x/10;
}
for(int i=len;i>=1;i--){
for(int j=0;j<=9;j++)
c[j]+=f[i-1]*num[i];
for(int j=0;j<num[i];j++)
c[j]+=ten[i-1];
ll num2=0;
for(int j=i-1;j>=1;j--){
num2=num2*10+num[j];
}
c[num[i]]+=num2+1;
c[0]-=ten[i-1];
}
}
signed main(){
a=read();
b=read();
ten[0]=1;
for(int i=1;i<=15;i++){
f[i]=f[i-1]*10+ten[i-1];
ten[i]=10*ten[i-1];
}
work(a-1,ca);
work(b,cb);
for(int i=0;i<=9;i++)
printf("%lld ",cb[i]-ca[i]);
return 0;
}
T2:\(P2657\)
找符合规定的数,显然是数位\(DP\)。
\(0\sim9\)都是\(windy\)数,所以先预处理一下
for(int i=0;i<=9;i++)
f[1][i]=1;
既然要做到每一位的递推,自然要把 \(a\) 和 \(b\) 拆开
while(x){
cf[++len]=x%10;
x/=10;
}
多举例子多算,易得状态转移方程。
对于以 \(i\) 为开头,长度为 \(j\) 的数,有
\[f[i][j]+=f[i-1][k];
\]
边界
数位\(DP\) 难就难在它的边界处理
我们不妨分情况讨论
1.数位小于 \(b\)
一加就好 \(:)\)
for(int i=1;i<=len-1;i++)
for(int j=1;j<=9;j++)
ans+=f[i][j];
2.数位等于 \(b\) 但最高位小于 \(b\) 的最高位
一加就好 \(:)\)
for(int i=1;i<cf[len];i++)
ans+=f[len][i];
3.数位等于 \(b\) 且最高位等于 \(b\) 的最高位
不太好搞 \(:(\)
需要从最高位后面开始枚举
for(int i=len-1;i>=1;i--)
for(int j=0;j<=cf[i]-1;j++)
if(abs(j-cf[i+1])>=2)
ans+=f[i][j];
于是乎,基本上做完了 \(qwq\)
代码
#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
#include<chrono>
#include<random>
#include<unordered_map>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rll register long long
#define ri register int
const int N=21000;
int a,b;
int f[N][N];
int work(int x){
int cf[N];
int len=0,ans=0;
while(x){
cf[++len]=x%10;
x/=10;
}
for(int i=1;i<=len-1;i++){
for(int j=1;j<=9;j++)
ans+=f[i][j];
}
for(int i=1;i<cf[len];i++)
ans+=f[len][i];
for(int i=len-1;i>=1;i--){
for(int j=0;j<=cf[i]-1;j++){
if(abs(j-cf[i+1])>=2)
ans+=f[i][j];
}
if(abs(cf[i+1]-cf[i])<2)
break;
}
return ans;
}
int main(){
scanf("%d%d",&a,&b);
for(int i=0;i<=9;i++)
f[1][i]=1;
for(int i=2;i<=10;i++)
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++)
if(abs(j-k)>=2)
f[i][j]+=f[i-1][k];
cout<<work(b+1)-work(a);
return 0;
}