本文主要是介绍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*/
这篇关于AtCoder Beginner Contest 338 G. evall(枚举+递推 统计贡献)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!