题解:P8022 [ONTAK2015] Cięcie
yizhishenjing · · 题解
题目描述
给定一个长度为
- 第一段能被
p 整除。 - 第二段能被
q 整除。 - 第三段能被
r 整除。 - 每一段不含前导零(单独的
0 是允许的)。
解决思路
我们设三段分别为
由于每一段不能有前导
- 第一段:如果长度大于
1 ,则第一个字符不能为0 ,如果长度为1 ,可以是0 。 - 第二段:如果长度大于
1 ,则s_i 不能为0 。 - 第三段:如果长度大于
1 ,则s_j 不能为0 。
直接枚举
首先注意到有前缀和后缀,所以考虑预处理前缀模和后缀模,用
然后我们发现
需要
将等式变形
其中
为了将问题转化为等值问题,可以引入逆元,设
逆元数组
定义
化简后得:
这样,我们就可以通过统计
关于枚举顺序,考虑枚举第一段结束位置
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 修改了一些公式推导的小问题(这次修改求管理通过)