对于数位dp,有个挺正常的套路。
首先一般拆分数字。将数字的每一位拿出来,作为上界。
然后设置状态,一般是填到第几位,然后根据题目设置,比如说windy就要看上一位是谁,不要62就看上一位是不是6之类的。
然后注意状态不能跟数字到底是谁有关。就是要确保后面可以乱放的情况下设置状态。
拆分完后从高位开始处理。一位一位,用limit来看是否能取到9,反之取到对应数位。然后加起来即可。
因为记忆化的原因,复杂度很小。
很水,但又前导0的限制,如果一直没有数,直到个位直接返回9。同时前后两位差绝对值>=2。然后走就行。
#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}
return cnt*f;
}int a,b;
int dp[14][12];
int cnt,num[123];
int dfs(int pos,int pre,int lead,int limit){
//cout<<pos<<" "<<pre<<" "<<lead<<" "<<limit<<endl;
if(pos==0)return 1;
if(!limit&&!lead&&dp[pos][pre]!=-1)return dp[pos][pre];
int tmp=0,up=limit?num[pos]:9;//cout<<up<<endl;
if(pos==1&&lead)return 9;
for(int i=0;i<=up;i++){
if(abs(pre-i)<2&&!lead)continue;
if(i==0&&lead)tmp+=dfs(pos-1,0,lead,limit&&i==num[pos]);
else tmp+=dfs(pos-1,i,0,limit&&i==num[pos]);
}if(!lead&&!limit)dp[pos][pre]=tmp;return tmp;
}
int solve(int x){
if(x<10)return x;
int cnt=0;
while(x){
num[++cnt]=x%10;x/=10;
}//cout<<cnt<<endl;
return dfs(cnt,0,1,1);
}
int main(){
memset(dp,-1,sizeof(dp));
a=in;b=in;
printf("%d\n",solve(b)-solve(a-1));
return 0;
}
只要不是4,前面是6这位不是2两个都满足就行。直接走。
#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
int dp[14][2];
int num[12],cnt;
int dfs(int pos,int is6,int limit){
if(pos==0)return 1;
if(!limit&&dp[pos][is6]!=-1)return dp[pos][is6];
int tem=0,up=limit?num[pos]:9;
for(int i=0;i<=up;i++){
if(i==4)continue;
if(is6&&i==2)continue;
tem+=dfs(pos-1,i==6,limit&&i==num[pos]);
}if(!limit)dp[pos][is6]=tem;return tem;
}
int solve(int x){
cnt=0;
while(x){
num[++cnt]=x%10;x/=10;
}return dfs(cnt,0,1);
}
int main(){
memset(dp,-1,sizeof(dp));
int a,b;a=in;b=in;
cout<<solve(b)-solve(a-1);
return 0;
}
首先二分出一个答案转为判定性问题。然后求1~x中符合条件的数的个数。
我们怎么确定一个数包含否子串呢?直接AC自动机啦。
设状态dp[pos][u][flag]表示当前在第几位,在AC自动机上的几号结点,是否已经包含子串。
然后每次走就是了。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
struct node{
int ch[10],key,fail;
}t[3003];int tot;
char ch[2300];
int l,r,k,n;
void insert(char s[],int len){
int rt=0;
for(int i=1;i<=len;i++){
int x=s[i]-'0';
if(!t[rt].ch[x]){
t[rt].ch[x]=++tot;
}rt=t[rt].ch[x];
}t[rt].key=1;
}
queue<int> q;
void getfail(){
for(int i=0;i<10;i++){
if(t[0].ch[i])q.push(t[0].ch[i]);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<10;i++){
if(t[u].ch[i]){
t[t[u].ch[i]].fail=t[t[u].fail].ch[i];q.push(t[u].ch[i]);
t[t[u].ch[i]].key|=t[t[t[u].ch[i]].fail].key;
}else{
t[u].ch[i]=t[t[u].fail].ch[i];
}
}
}
}
int num[23],cnt;
int dp[23][1503][2];
int dfs(int pos,int u,int flag,int limit,int lead){
if(pos==0)return flag?1:0;
if(!limit&&!lead&&dp[pos][u][flag]!=-1)return dp[pos][u][flag];
int tem=0,up=limit?num[pos]:9;
for(int i=0;i<=up;i++){
if(lead&&i==0&&pos>1){
tem+=dfs(pos-1,0,flag,limit&&i==num[pos],1);
}else tem+=dfs(pos-1,t[u].ch[i],flag|t[t[u].ch[i]].key,limit&&i==num[pos],lead&&i==0);
}
if(!limit&&!lead)dp[pos][u][flag]=tem;return tem;
}
int check(int x){//cout<<x<<" ";
cnt=0;memset(num,0,sizeof(num));
while(x){
num[++cnt]=x%10;x/=10;
}memset(dp,-1,sizeof(dp));
int ans=dfs(cnt,0,0,1,1);
//cout<<ans<<endl;
return ans;
}
signed main(){
l=in;r=in;k=in;n=in;
for(int i=1;i<=n;i++){
scanf("%s",ch+1);int len=strlen(ch+1);
insert(ch,len);
}getfail();
//for(int i=1;i<=tot;i++)cout<<t[i].key<<" ";cout<<endl;
int L=l,R=r+1,kk=check(l-1);
//cout<<kk<<endl;
while(L<R){
int mid=(L+R)>>1;
if(check(mid)-kk>=k)R=mid;
else L=mid+1;
}if(L==r+1)cout<<"no such number";
else cout<<L;
return 0;
}
暴力搞吧。直接枚举哪一位是力矩,因为这一位是力矩,下一位就肯定不是。所以不重不漏。
注意有负数,整体挪动3000位,反正开的下。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
int f[20][20][6000];int num[20],cnt;
int dfs(int pos,int mid,int sum,int lead,int limit){
if(!pos)return (sum==3000&&!lead);
if(!limit&&!lead&&f[pos][mid][sum]!=-1)return f[pos][mid][sum];
int tem=0,up=limit?num[pos]:9;
for(int i=0;i<=up;i++){
tem+=dfs(pos-1,mid,sum+(pos-mid)*i,lead&i==0,limit&(i==num[pos]));
}if(!limit&&!lead)f[pos][mid][sum]=tem;
return tem;
}
int solve(int x){
if(x<=0)return x;
cnt=0;
while(x){
num[++cnt]=x%10;x/=10;
}
int ans=0;
for(int i=1;i<=cnt;i++){
ans+=dfs(cnt,i,3000,1,1);
}return ans;
}
signed main(){
int t=in;memset(f,-1,sizeof(f));
while(t--){
int a=in;int b=in;
cout<<solve(b)-solve(a-1)<<'\n';
}
return 0;
}