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]\) 转移时可以分成两类:

  1. \(s[i]\) 单独划分,贡献为 \(f[i-1]s[i]\)
  2. \(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;
}
posted @ 2022-03-28 19:25  Zinn  阅读(21)  评论(0编辑  收藏  举报