1 条题解

  • 0
    @ 2025-8-24 22:35:13

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar yizhishenjing
    深鲸

    搬运于2025-08-24 22:35:13,当前版本为作者最后更新于2025-07-22 16:34:10,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目描述

    给定一个长度为 kk 的数字串 NN 和三个质数 ppqqrr,需要将数字串划分为三段非空子串,使得:

    • 第一段能被 pp 整除。
    • 第二段能被 qq 整除。
    • 第三段能被 rr 整除。
    • 每一段不含前导零(单独的 00 是允许的)。

    解决思路

    我们设三段分别为 [0,i1][0,i-1][i,j1][i,j-1][j,k1][j,k-1]

    由于每一段不能有前导 00,因此:

    • 第一段:如果长度大于 11,则第一个字符不能为 00,如果长度为 11,可以是 00
    • 第二段:如果长度大于 11,则 sis_i 不能为 00
    • 第三段:如果长度大于 11,则 sjs_j 不能为 00

    直接枚举 iijj 的话,时间复杂度为 O(k2)O(k^2),肯定会炸掉,因此考虑优化。

    首先注意到有前缀和后缀,所以考虑预处理前缀模和后缀模,用 sumpisump_i 表示子串 s0i1s_{0 \ldots i-1}pp 的值,用 sumqisumq_i 表示子串 s0i1s_{0 \ldots i-1}qq 的值,用 hourihour_i 表示子串 sik1s_{i \ldots k-1}rr 的值。

    然后我们发现 sumqisumq_i 有前缀影响,所以我们需要消除前缀影响,并且需要快速判断是否存在 jj 使得满足条件。

    Bmodq=(sumqjsumqi×10ji)modqB \bmod q=(sumq_j-sumq_i \times 10^{j-i}) \bmod q

    需要 Bmodq=0B \bmod q=0,则:

    sumqjsumqi×10jimodqsumq_j \equiv sumq_i \times10^{j-i} \bmod q

    将等式变形

    $$sumq_i \equiv sumq_j \times (10^{j-i})^{-1} \bmod q $$

    其中 (10ji)1(10^{j-i})^{-1}10ji10^{j-i} 在模 qq 下的逆元

    为了将问题转化为等值问题,可以引入逆元,设 d=jid=j-i

    逆元数组 cird=(10d)1modqcir_d=(10^d)^{-1} \bmod q ,满足:

    $$sumq_i \times (10^j)^{-1} \equiv sumq_j \times cir_d \times (10^j)^{-1} $$

    定义

    hashk=sumqk×(10k)1modqhash_k=sumq_k \times (10^k)^{-1} \bmod q

    化简后得:

    hashihashjmodqhash_i \equiv hash_j \bmod q

    这样,我们就可以通过统计 hashjhash_j 的值来快速找到符合条件的 jj

    关于枚举顺序,考虑枚举第一段结束位置 ii 并动态维护 jj 的位置,用指针 lastlast 从后向前扫描,将满足合法 jjhashjhash_j 丢进数组里,最后再分类讨论一下就做完了。

    Ac Code

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    int k,p,q,r,sump[1992929],sumq[1992929],hour[1992929],zhir[1992929];
    
    int hash1[1992929],pw[1992929],cir[1992929],ans1[1992929];
    
    string s;
    int ksm(int a,int b,int mod){//快速幂 
    	int res=1;
    	while(b){
    		if(b&1) res=(res*a)%mod;
    		a=(a*a)%mod,b>>=1;
    	}
    	return res;
    }
    
    signed main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        
        cin>>k>>p>>q>>r;
        
        cin>>s;
        //预处理前缀模值 
        for(int i=1;i<=k;i++){
        	
        	sump[i]=((sump[i-1]*10)%p+(s[i-1]-'0'))%p;
        	
        	sumq[i]=((sumq[i-1]*10)%q+(s[i-1]-'0'))%q;
        	
    	}
    	//预处理后缀模值 
    	int base=1;
    	
    	for(int i=k-1;i>=0;i--){
    		
    		hour[i]=((s[i]-'0')*base+hour[i+1])%r;
    		
    		base=(base*10)%r;
    		
    	}
    	//验证第三段的合法性 
    	for(int i=0;i<k;i++){//表示从i开始的第三段是否合法 
    		
    		int len=k-i;//第三段长度 
    		
    		if(len==1){
    			zhir[i]=(hour[i]%r==0);
    		}else{
    			zhir[i]=((hour[i]%r==0)&&(s[i]!='0'));
    		}
    		
    	}
    	
    	pw[0]=1;//预处理10的幂 
    	for(int i=1;i<=k;i++){
    		pw[i]=(pw[i-1]*10)%q;
    	}
    	//求逆元 
    	for(int i=0;i<=k;i++){
    		cir[i]=ksm(pw[i],q-2,q);//// 费马小定理求逆元
    	}
    	
    	for(int i=0;i<=k;i++){ 
    		hash1[i]=(sumq[i]*cir[i])%q;
    	}
    	//枚举划分方案 
    	int ans=0,last=k-1;
    	
    	for(int i=k-2;i>=1;i--){//倒序枚举第一段结束位置i(第一段长度i)
    		
    		if(i>1&&s[0]=='0')continue;
    		
    		if(sump[i]%p!=0)continue;
    		//将满足j>=i+2的合法第三段起始位置j加入ans1 
    		while(last>=i+2){
    			
    			if(zhir[last]){
    				
    				int H=hash1[last];
    				
    				while(H<0)H+=q;
    				
    				H%=q;
    				
    				ans1[H]++;
    			}
    			
    			last--;
    			
    		}
    		 //情况1:第二段长度为1(j=i+1)
    		if(i+1<k){
    			
    			if(zhir[i+1]){
    				
    				if(s[i]=='0'){
    					
    					ans++;
    					
    				}
    			}
    		}
    		//情况2:第二段长度>1
    		if(s[i]!='0'){
    			
    			int H=hash1[i];
    			
    			while(H<0)H+=q;
    			
    			H%=q;
    			
    			ans+=ans1[H];
    			
    		}
    	}
    	cout<<ans;
        return 0;
    }
    

    2025-07-29 修改了一些公式推导的小问题(这次修改求管理通过)

    • 1

    信息

    ID
    7373
    时间
    1000ms
    内存
    256MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者