ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

数位Dp

2022-08-13 12:03:02  阅读:178  来源: 互联网

标签:后缀 int vis 1% c2 数位 Dp mod


代码拍卖会

题意

问有[ L - R ]有多少个数满足每一位都至少有 1,从左到右不减同时要能被P整除,位数<=\(1e18\). p<=500)

思路

  1. 位数贼大,基本上别想着枚举有关位数的东西
  2. 单调不减,说明什么,说明一个合法的方法我前面的 +1 后面的至少要 +1 ,如果把他们看成最多 9个 长度为 n的01串的和,其中这些串满足 后缀全是1。这样可以保证满足条件的情况下,还能保证单调不减。
  3. 现在问题再处理 长度为n的01串怎么处理,因为根本不可能把他枚举出来,但是这个最后要满足是P的倍数,所以我们加上一个 11 %2 和 1 %2 的意义其实是一样的,考虑把 mod P 相同的归为一类,最后把选择一个P的情况归为在 K 个中挑X的方案数,K是在mod P意义下相同的个数。

找循环节

int c=1%p;//C是当前的 后缀情况 1  11  111 1111 
int cnt=1;//没啥用
c2[0]=1%p;// 循环节中第i个位置
vis[1%p]=0;//  表示 某个后缀在循环中的位置
for(int i=1;;i++)
{
   c2[i]=(c2[i-1]*10+1)%p;//每次在后面填一个1 记得取mod
   if(vis[c2[i]]!=-1)//如果出现过。就准备break
   {
       for(int j=0;j<min(i,n);j++)g[c2[j]]++,f=c2[j];//把当前循环节的数目+1
       // F是 全部是1的情况,因为要保证他每一位起码是1 ,
       //所以我循环中的最后一个有一定可能是全是1 的情况,如果没有循环节的话。
       //因为他可能i大于n 就是找到循环的情况比n的长度还长。
       n-=i;//因为 已经加了i个的情况了,
       st=vis[c2[i]];//st 表示初始也就是 第一次出现两次的情况
       m=(i-st);// 这个是循环节长度,
       break;
   }
   vis[c2[i]]=i;//标记一下 当前后缀是第几个出现的。
}

预处理G数组

n=max(0ll,n);
int time=n/m;
// m是循环节的长度
for(int i=0;i<m;i++)
{
    g[c2[i+st]]+=time;
    //  找到循环节 一次性全加上
}

int mod2=n%m;//把多余的部分暴力加上1
for(int i=0;i<mod2;i++)
{
    g[c2[i+st]]++;
    f=c2[st+i];	
    //这个地方,只要我找到最后的一个情况,那么这个肯定就是 全是1的情况。
}
for(int i=0;i<p;i++)g[i]%=mod;

Dp状态定义

\(D[i][j][k]\)表示当前已经选到了 mod P等于 i 的位置,同时选了 j 个 同时 mod p 等于 k 的方案数。

// 为啥要倒过来捏。因为 如果你是正着来,你mod p等于0的情况需要用到,但是你很难处理,所以索性倒过来处理。
枚举当前选了j个同时是由 (0-(i-1))中选出 j2 个转移过来的。
d[i][j2+j][k+(j2*i)%mod]=d[i+1][j2][k]*C(从 g[i] 个中选出j2个的方案数)。
然后就结束了。

Code

const int N=600,mod=999911659;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}
int d[N][10][N];
//表示当前已经选到了 mod P等于 i 的位置,同时选了 j 个 同时 mod p 等于 k 的方案数。
int g[N];//mod p 等于 i 的方案数
int vis[N];//一个后缀在循环节中出现的位置
int c2[N];//第i个后缀的值

int qmi(int x,int k)
{
	int res=1;
	while(k)
	{
		if(k&1)res=res*x%mod;
		k>>=1;
		x=x*x%mod;
	}
	return res;
}

int C(int a,int b)
{
	if(a<b)return 0;
	int down=1,up=1;
	for(int i=a,j=1;j<=b;j++,i--)
	{
		up=up*i%mod;
		down=down*j%mod;
	}
	return up*qmi(down,mod-2)%mod;
}

void solve()
{
	int n,p;cin>>n>>p;
	memset(vis,-1,sizeof vis);
	int f=0;
	int st=0,m=0;
	int c=1%p;
	int cnt=1;
	c2[0]=1%p;
	vis[1%p]=0;
	int c=1%p;//C是当前的 后缀情况 1  11  111 1111 
    int cnt=1;//没啥用
    c2[0]=1%p;// 循环节中第i个位置
    vis[1%p]=0;//  表示 某个后缀在循环中的位置
    for(int i=1;;i++)
    {
        c2[i]=(c2[i-1]*10+1)%p;//每次在后面填一个1 记得取mod
        if(vis[c2[i]]!=-1)//如果出现过。就准备break
        {
            for(int j=0;j<min(i,n);j++)g[c2[j]]++,f=c2[j];
            //把当前循环节的数目+1
            // F是 全部是1的情况,因为要保证他每一位起码是1 ,
            //所以我循环中的最后一个有一定可能是全是1 的情况,如果没有循环节的话。
            //因为他可能i大于n 就是找到循环的情况比n的长度还长。
            n-=i;//因为 已经加了i个的情况了,
            st=vis[c2[i]];//st 表示初始也就是 第一次出现两次的情况
            m=(i-st);// 这个是循环节长度,
            break;
        }
        vis[c2[i]]=i;//标记一下 当前后缀是第几个出现的。
    }
	n=max(0ll,n);
    int time=n/m;
    // m是循环节的长度
    for(int i=0;i<m;i++)
    {
        g[c2[i+st]]+=time;
        //  找到循环节 一次性全加上
    }

    int mod2=n%m;//把多余的部分暴力加上1
    for(int i=0;i<mod2;i++)
    {
        g[c2[i+st]]++;
        f=c2[st+i];	
        //这个地方,只要我找到最后的一个情况,那么这个肯定就是 全是1的情况。
    }
    for(int i=0;i<p;i++)g[i]%=mod;
	// 当前美枚举到第i位  ,用了j个 同时余数为k的
	// cout<<f<<endl;
	d[p+1][0][f]=1;
	for(int i=0;i<p;i++)
	{
		for(int j=0;j<9;j++)
		{
			for(int s=0;s+j<9;s++)
			{
				int mul=C(g[i]+s-1,s);
				if(!s)mul=1;//emm 从0个中选出0个在这里是合法的。
				for(int d2=0;d2<p;d2++)
				{
					d[p-i][j+s][(d2+s*i)%p]=(d[p-i][j+s][(d2+s*i)%p]+mul*(d[p-i+1][j][d2])%mod)%mod;
					
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<9;i++)
	{
		ans=(ans+d[1][i][0])%mod;//最后只要看选了几个就可以了。
	}
	cout<<ans<<endl;
	
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
} 

标签:后缀,int,vis,1%,c2,数位,Dp,mod
来源: https://www.cnblogs.com/hxxO-o/p/16582721.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有