2022春训第三场
[A]
题意:
给一个长度为 \(n\) 的数字串(只含 \(1-9\)),定义一个划分的权值是各部分数的乘积,求所有划分的权值和。 \(1 \leq n \leq 2*10^5\) 。
分析:
试图分治+map记忆化,但是T了。
代码如下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mid ((l+r)>>1)
int const mod=998244353, N=2e5+5;
int n,a[N];
char s[N];
struct Pr{
int l,r;
bool operator < (const Pr &b) const
{return (l!=b.l)?l<b.l:r<b.r;}
Pr(int L,int R){l=L; r=R;}
};
map<Pr,ll> mp;
ll get(int l,int r)
{
ll ret=a[l];
for(int i=l+1;i<=r;i++) ret=((ret*10)+a[i])%mod;
return ret;
}
ll mo(ll x){return x>=mod?x-mod:x;}
ll pw(ll x,int y)
{
ll ret=1;
while(y)
{
if(y&1) ret=(ret*x)%mod;
x = (x*x)%mod;
y>>=1;
}
return ret;
}
ll f(int l,int r)
{
if(l>r)return 1;
if(l==r)return a[l];
if(mp[Pr(l,r)]) return mp[Pr(l,r)];
ll ret=f(l,mid)*f(mid+1,r)%mod;
ll pre=0;
for(int L=mid;L>=l;L--)
{
// ll num=get(L,mid);
ll num = mo(pre+a[L]*pw(10,mid-L)%mod);
pre=num;
for(int R=mid+1;R<=r;R++)
{
num = ((num*10)+a[R])%mod;
// printf("%lld + (%lld*%lld) * %lld ",ret,f(l,L-1),num,f(R+1,r));
ret = mo(ret + (f(l,L-1) * num % mod) * f(R+1,r) % mod);
// printf("L=%d R=%d num=%d ret=%lld\n",L,R,num,ret);
}
}
// printf("f(%d,%d)=%lld\n",l,r,ret);
return mp[Pr(l,r)]=ret;
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
mp.clear();
scanf("%s",s+1); n=strlen(s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'0';
printf("%lld\n",f(1,n));
}
return 0;
}
但实际上可以直接按顺序DP,设 \(f[i]\) 为前 \(i\) 位的所有划分权值和,\(f[i]\) 转移时可以分成两类:
- \(s[i]\) 单独划分,贡献为 \(f[i-1]s[i]\) ;
- \(s[i]\) 和前 \(i-1\) 个元素的划分中的最后一个数 \(x\) 分到一起。假设最后一个数 \(x\) 对应的划分权值为 \(P\),则此种划分的贡献为 \(\frac{P(10x+s[i])}{x} = 10P + s[i]\frac{P}{x}\) ;则所有划分的总贡献为 $ 10f[i-1] + s[i]\sum_{j=0}^{i-2}f[j]$ ,记录 \(f\) 的前缀和即可求。
注意初值 \(f[0]=1\) 。
时间复杂度 \(O(n)\) 。
代码如下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int const N=2e5+5,mod=998244353;
ll f[N],fs[N];
char st[N];
ll pls(ll x,ll y){return (x+y>=mod)?x+y-mod:x+y;}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%s",st+1); int n=strlen(st+1);
f[0]=1; fs[0]=1;
f[1]=st[1]-'0'; fs[1]=f[1]+1;
// f[2]=f[1]*(st[2]-'0') + f[1]*10+(st[2]-'0'); fs[2]=fs[1]+f[2]; num[2]=2;
for(int i=2;i<=n;i++)
{
ll t=st[i]-'0';
f[i] = (f[i-1]*t%mod + 10*f[i-1]%mod + fs[i-2]*t%mod)%mod;
fs[i] = pls(fs[i-1], f[i]);
// printf("f[%d]=%lld fs[%d]=%lld\n",i,f[i],i,fs[i]);
}
printf("%lld\n",f[n]);
}
return 0;
}
[D]
题意:
给出 \(k\) 和一个序列,求有多少个子区间的和是 \(k\) 的倍数。 $ 1 \leq n \leq 10^5, 1 \leq k \leq 10^9 $ 。
分析:
签到题,直接用 \(map\) 记录前面元素的前缀和就行了,或者枚举 \(k\) 的幂次然后双指针。
但是没过。要注意 \(k=1\) 的情况,还要注意一直累加\(k\)的幂次来比较的话可能会爆 \(long\ long\) ,要用 \(\underline{ }\underline{ }int128\) 。
代码如下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int const N=1e5+5;
int n,k;
ll a[N];
map<ll,int>mp;
int main()
{
int T; scanf("%d",&T);
while(T--)
{
mp.clear(); mp[0]++;
ll ans=0;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]); a[i]+=a[i-1];
mp[a[i]]++;
if(k==1) ans+=mp[a[i]-1];
else
{
__int128 nw=k;
while(nw<=a[i]) ans+=mp[a[i]-nw], nw*=k;//爆longlong
}
}
printf("%lld\n",ans);
}
return 0;
}