AtCoder Beginner Contest 338 G. evall(枚举+递推 统计贡献)

题目

长为1e6的串,只由数字1-9、加号+、乘号*组成,

对1<=i<=j<=n,只要s[i]和s[j]均为数字,就将eval(i,j)(求值表达式)的值加入答案内

求答案对998244353取模的值

换言之,求以下程序的结果

s = input()
n = len(s)
ans, mod = 0, 998244353
def ok(x):
    return '0'<=x<='9'
for i in range(n):
    for j in range(i,n):
        if ok(s[i]) and ok(s[j]):
            ans = (ans + eval(s[i:j+1])) % mod
print(ans)

思路来源

乱搞ac

题解

把连乘的段单独处理,统计每一段的贡献

如果左侧顶到一个+号,那么加号往左的都可以取,右侧同理

如果左侧不是加号,那么左侧只能卡到i 

1234*2345*3456

枚举i=2时,这一位的贡献有:

2 23 234

234*2 234*23 234*234 234*2345

234*2345*3 234*2345*34 234*2345*345 234*2345*3456

发现需要处理一个形如(3+34+345+3456)的东西,

还需要处理一个形如2345*(3+34+345+3456)的东西,并加上2+23+234

于是开了一堆数组维护这些内容,

pre[i]表示i前有几个数字,suf[i]表示i后有几个数字

尺取相邻加号之间的段,处理连乘的贡献,

不妨称3+34+345+3456这样的段,叫3456这个数的前缀贡献

sum[i]形如(3+34+345+3456)+(2+23+234+2345),

只需要某个数的前缀贡献的时候,作差得到

sum2[i]形如2+23+234+2345*(3+34+345+3456),

转移要么乘上一个数,要么加上这个数的前缀贡献,可以递推实现

由4+45+456需要得到3+34+345+3456时,加上3333即可,

所以ten[i]维护10的幂次,形如1000,sten[i]维护其前缀和,形如1111

las[i]记录i这个位置后面的第一个符号的位置,可能为乘号或者加号

now[i]记录i这个位置所在的段往后延伸到尾部时,这个数字是多少

mul[i]表示i这个位置往后的第一个完整的数到尺取段尾这一段区间的积是多少

比如1+23*45*56+7,mul[4]表示45*56的值,而mul[3]表示23*45*56的值

总之,就是通过手玩发现,需要通过枚举+数贡献的方式计数,然后就是xjb乱搞

代码

#include<bits/stdc++.h>
#include<cstdio>
#include<vector>
#include<map>
#include<queue>
#include<array>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
const int N=1e6+10,mod=998244353;
char s[N];
int n,pre[N],suf[N],sum[N],sum2[N],mul[N];
int ten[N],sten[N],cur,w,las[N],now[N],ans;
void add(int &x,int y){
    x=(x+y)%mod;
}
bool ok(char x){
    return '0'<=x && x<='9';
}
int cal(int l,int r){
    return (sum[l]-sum[r+1]+mod)%mod;
}
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    s[0]=s[n+1]='+';
    ten[0]=sten[0]=1;
    rep(i,1,n){
        ten[i]=10ll*ten[i-1]%mod;
        sten[i]=(sten[i-1]+ten[i])%mod;
    }
    rep(i,1,n){
        pre[i]=pre[i-1]+ok(s[i]);
    }
    per(i,n,1){
        suf[i]=suf[i+1]+ok(s[i]);
    }
    for(int j=n+1;j>=0;){
        if(s[j]=='+'){
            mul[j]=1;
            las[j]=j;
            j--;
            continue;
        }
        int k=j;
        while(k && s[k]!='+')k--;
        cur=w=0;
        for(int i=j;i>k;--i){
            las[i]=las[i+1];
            sum[i]=sum[i+1];
            sum2[i]=sum2[i+1];
            mul[i]=mul[i+1];
            if(s[i]=='*'){
                cur=w=0;
                las[i]=i;
                continue;
            }
            cur++;
            int v=s[i]-'0';
            w=(w+1ll*v*ten[cur-1]%mod)%mod;
            now[i]=w;
            sum[i]=(sum[i+1]+1ll*v*sten[cur-1]%mod)%mod;
            //printf("i:%d sum2i:%d\n",i,sum2[i]);
            if(!ok(s[i-1])){
                sum2[i]=cal(i,las[i]-1)%mod;
                sum2[i]=(sum2[i]+1ll*w*sum2[las[i]]%mod)%mod;
                mul[i]=1ll*mul[i]*w%mod;
            }
            //printf("i:%d sum:%d sum2:%d las:%d cal:%d w:%d pre:%d\n",i,sum[i],sum2[i],las[i],cal(i,las[i]-1),w,sum2[las[i]]);
        }
        for(int i=k+1;i<=j;++i){
            if(s[i]=='*')continue;
            int w1=1ll*now[i]*sum2[las[i]]%mod;//*后面的sum2 和*前面结尾1的贡献 左端点在i 右端点在las[i]+号左的贡献
            int bs=(i==k+1?pre[k]+1:1);//左侧+左的也可以取(i=k+1时可以再往左任取)
            int w2=1ll*now[i]*mul[las[i]]%mod*suf[j+1]%mod;//右侧+右的也可以取 左端点在i 右端点在las[i]+号右的贡献
            int w3=cal(i,las[i]-1);
            w=(w1+w2)%mod;w=(w+w3)%mod;
            w=1ll*w*bs%mod;
            ans=(ans+w)%mod;
            //printf("i:%d s:%d las:%d now:%d bs:%d sum2lasi:%d w1:%d w2:%d w3:%d w:%d ans:%d\n",i,s[i]-'0',las[i],now[i],bs,sum2[las[i]],w1,w2,w3,w,ans);
        }
        j=k;
    }
    printf("%d\n",ans);
    return 0;
}
//2*(2+2*3+2*34)
/*
1*mul[2]
1*2*mul[3]
1*2*3*mul[4]
mul[3]+3*mul[4]
当前数做积

2345*(3+34+345+3456)
+2+23+234+2345
每一段先乘完整数 然后加mul
两层递推
*/
/*
1+10*11+2
1 (2)
10 (2) 
10*1 (2)
10*11 (4)
*/

/*8 8*3 8*3*3 8*3*33*/

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值