CF2053I Affectionate Arrays 题解
IvanZhang2009 · · 题解
注意到
- 考虑一个前缀
a_{1\dots k} 的和<0 ,则取后面的部分的和超过了s 。 - 考虑一个前缀
a_{1\dots k} 的和>s ,则取这段前缀的和超过了s 。 - 满足值域限制的条件下,一个子段和可以描述成两个前缀和相减的形式,显然这个差也在
[-s,s] 中。
所以这是充要的。我们考虑直接对前缀和设计 dp:
考虑把
下面是代码。很短吧!
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define all(v) v.begin(),v.end()
#define pb push_back
#define REP(i,b,e) for(int i=(b);i<(int)(e);++i)
#define over(x) {cout<<(x)<<endl;return;}
int n,s;
int a[3000005];
void Main() {
cin>>n;s=0;
REP(i,0,n)cin>>a[i],s+=a[i];
int cur=0,l=0,r=0;
REP(i,0,n){
int L=max(0ll,-a[i]),R=min(s,s-a[i]);
if(max(L,l)<=min(r,R))l=max(l,L),r=min(r,R);
else l=L,r=R,++cur;
l+=a[i];r+=a[i];
l=max(l,0ll);r=min(r,s);
if(l>r)l=0,r=s,++cur;
}
over(cur+n+(r!=s))
}
void TC() {
int tc=1;
cin>>tc;
while(tc--){
Main();
cout.flush();
}
}
signed main() {
return cin.tie(0),cout.tie(0),ios::sync_with_stdio(0),TC(),0;
}
当然这篇题解到这里还没有结束。原题是一个计数,但是因为各种原因被删掉了。这里也一起分享出来。
update:I2 改成符合 std 的题意之后回归了!所以我们改变一下说法。考虑现在的新题意怎么做!
原 std 的做法针对“数合法序列
新版题意改成了“
扩展 I1 的做法,考虑方案数的转移,设
总结一下就是前后缀删点,前后缀加值为
我们观察转移的过程,发现少有单点操作,都是对着整段的区间做操作,例如前后缀塞一个连续段,前后缀删连续段,这启发我们不仅是 deque 维护所有连续段的所有信息,包括左右端点和每个点的 push,pop 操作的同时维护出来。一个大细节是 pop 区间后可能会出现全局 push 次数为
给出我的实现:
#include<bits/stdc++.h>
using namespace std;
#define MOD 998244353
#define int long long
#define pii pair<int,int>
#define all(v) v.begin(),v.end()
#define pb push_back
#define REP(i,b,e) for(int i=(b);i<(int)(e);++i)
#define over(x) {cout<<(x)<<endl;return;}
#define deal(v) sort(all(v));v.erase(unique(v.begin(),v.end()),v.end())
#define lbound(v,x) lower_bound(all(v),x)-v.begin()
struct interval{
int l,r,g;
int calc(int tg=0){return ((r-l+1)*(tg+g))%MOD;}
};
int n,s;
int a[3000005];
#define inside(x) (x.l>=l&&x.r<=r)
void Main() {
cin>>n;s=0;
REP(i,0,n)cin>>a[i],s+=a[i];
if(s<*max_element(a,a+n))assert(0)
if(s<-*min_element(a,a+n))assert(0);
if(!s)over(1)
deque<interval>dq;
dq.pb({0,0,1});dq.pb({1,s,1});
int tpos=0,tval=0,l=0,r=0,sum1=1,sum2=s,tv2=0;
REP(i,0,n){
if(a[i]>=0){
while(dq.size()&&dq.back().l+tpos>s-a[i]){
if(inside(dq.back()))sum1-=dq.back().calc(tv2);
else sum2-=dq.back().calc(tval);
dq.pop_back();
}
if(dq.back().r+tpos>s-a[i]){
if(inside(dq.back()))sum1-=(dq.back().g+tv2)*(dq.back().r+tpos-(s-a[i]))%MOD;
else sum2-=(dq.back().r+tpos-(s-a[i]))*(tval+dq.back().g)%MOD;
dq.back().r=s-a[i]-tpos;
}
sum1=(MOD+sum1%MOD)%MOD;
sum2=(MOD+sum2%MOD)%MOD;
r=min(r,s-a[i]-tpos);
if(l>r){
l=dq.front().l;r=dq.back().r;
sum1=sum2;sum2=0;tv2=tval;tval=0;
}
if(a[i])dq.push_front({dq[0].l-a[i],dq[0].l-1,(MOD-tval)%MOD});
tpos+=a[i];(tval+=sum1)%=MOD;(sum2+=sum1*(s+1-(r-l+1)))%=MOD;
}else{
a[i]*=-1;
while(dq.size()&&dq.front().r+tpos<a[i]){
if(inside(dq.front()))sum1-=dq.front().calc(tv2);
else sum2-=dq.front().calc(tval);
dq.pop_front();
}
if(dq.front().l+tpos<a[i]){
if(inside(dq.front()))sum1-=(dq.front().g+tv2)*(a[i]-dq.front().l-tpos)%MOD;
else sum2-=(a[i]-dq.front().l-tpos)*(dq.front().g+tval)%MOD;
dq.front().l=a[i]-tpos;
}
sum1=(MOD+sum1%MOD)%MOD;
sum2=(MOD+sum2%MOD)%MOD;
l=max(l,a[i]-tpos);
if(l>r){
l=dq.front().l;r=dq.back().r;
sum1=sum2;sum2=0;tv2=tval;tval=0;
}
dq.push_back({dq.back().r+1,dq.back().r+a[i],(MOD-tval)%MOD});
tpos-=a[i];(tval+=sum1)%=MOD;(sum2+=sum1*(s+1-(r-l+1)))%=MOD;
}
}
cout<<(dq.back().g+(inside(dq.back())?tv2:tval))%MOD<<endl;
}
void TC() {
int tc=1;
cin>>tc;
while(tc--){
Main();
cout.flush();
}
}
signed main() {
return cin.tie(0),cout.tie(0),ios::sync_with_stdio(0),TC(),0;
}
其实这篇题解到这里还是没有结束。作为名义上的 writer,出锅之后我还得修,于是考虑原版题意的计数。能否改进 std 的做法呢?尝试对于同一个数的连续段 dp,发现似乎很困难。irris 和我都放弃了。
我们从问题的本质上入手。不妨直接对着序列
int n;
int a[3000005];
int dp[105][105][1005];
int s;
int qlen(){
int cur=0,l=0,r=0;
REP(i,0,n){
int L=max(0ll,-a[i]),R=min(s,s-a[i]);
if(max(L,l)<=min(r,R))l=max(l,L),r=min(r,R);
else l=L,r=R,++cur;
l+=a[i];r+=a[i];
l=max(l,0ll);r=min(r,s);
if(l>r)l=0,r=s,++cur;
}
return cur+(r<s)+n;
}
void Main() {
cin>>n;
REP(i,0,n)cin>>a[i];
s=accumulate(a,a+n,0ll);
int m=qlen();
REP(i,0,m+1){
REP(j,0,n+1){
REP(l,0,s+1)dp[i][j][l]=0;
}
}
dp[0][0][0]=1;
REP(i,0,m){
REP(j,0,n+1){
REP(l,0,s+1)if(dp[i][j][l]){
REP(p,-l,s-l+1){
int j2=j+(j<n&&a[j]==p);
(dp[i+1][j2][l+p]+=dp[i][j][l])%=MOD;
}
}
}
}
cout<<dp[m][n][s]<<endl;
}
可以发现这个
int n;
int a[3000005];
int dp[105][105][1005];
int s;
int qlen();//和上面相同,省略
void Main() {
cin>>n;
REP(i,0,n)cin>>a[i];
s=accumulate(a,a+n,0ll);
int m=qlen();
REP(i,0,m+1){
REP(j,0,n+1){
REP(l,0,s+1)dp[i][j][l]=0;
}
}
dp[0][0][0]=1;
REP(i,0,m){
REP(j,0,n+1){
int sum=0;
REP(l,0,s+1){
sum+=dp[i][j][l];
}
REP(l,0,s+1){
dp[i+1][j][l]=sum;
if(j<n&&l>=a[j]&&l-a[j]<=s)dp[i+1][j][l]-=dp[i][j][l-a[j]];
if(j>0&&l>=a[j-1]&&l-a[j-1]<=s)dp[i+1][j][l]+=dp[i][j-1][l-a[j-1]];
(dp[i+1][j][l]+=MOD)%=MOD;
}
}
}
cout<<dp[m][n][s]<<endl;
}
考虑原做法的连续段想法。我们把
int n;
int a[3000005];
int s;
struct structures{
int l,r,val;
};
#define vs vector<structures>
vs f[30005],g[30005];
int qlen();//和上面相同,省略
vs mover(vs a,int x){
if(!x)return a;
vs b;
if(x>0)b.pb({0,x-1,0});
for(auto [l,r,val]:a){
int L=l+x,R=r+x;
L=max(L,0ll);R=min(R,s);
if(L<=R)b.pb({L,R,val});
}
if(x<0)b.pb({s+x+1,s,0});
return b;
}
vs operator -(vs a,vs b){
vector<int>t;
int x=0,y=0,n=a.size(),m=b.size();
while(x<n&&y<m){
if(a[x].l==b[y].l)t.pb(a[x].l),++x,++y;
else if(a[x].l<b[y].l)t.pb(a[x++].l);
else t.pb(b[y++].l);
}
while(x<n)t.pb(a[x++].l);
while(y<m)t.pb(b[y++].l);
n=t.size();t.pb(s+1);
vs c(n,{0,0,0});
REP(i,0,n)c[i].l=t[i],c[i].r=t[i+1]-1;
x=0;
for(auto [l,r,val]:a){
while(c[x].r<l)++x;
while(x<n&&c[x].l>=l&&c[x].r<=r)c[x].val+=val,++x;
}
x=0;
for(auto [l,r,val]:b){
while(c[x].r<l)++x;
while(x<n&&c[x].l>=l&&c[x].r<=r)c[x].val+=MOD-val,++x;
}
REP(i,0,n)c[i].val%=MOD;
a.clear();
int lstval=-1;
for(auto [l,r,val]:c){
if(val==lstval)a[a.size()-1].r=r;
else a.pb({l,r,lstval=val});
}
return a;
}
void Main() {
cin>>n;
REP(i,0,n)cin>>a[i];
s=accumulate(a,a+n,0ll);
int m=qlen();
REP(i,0,n+1){
f[i].clear();
g[i].clear();
if(i)g[i]=vs(1,{0,s,0});
else{
g[i].pb({0,0,1});g[i].pb({1,s,0});
}
}
REP(i,0,m){
for(int j=n;j>=0;--j){
int sum=0;
for(auto [l,r,val]:g[j])(sum+=(r-l+1)%MOD*val)%=MOD;
vs s1=j<n? mover(g[j],a[j]):vs(1,{0,s,0}),s2=j? mover(g[j-1],a[j-1]):vs(1,{0,s,0});
g[j]=s2-s1;
for(auto &l:g[j])(l.val+=sum)%=MOD;
}
}
int x=g[n].size()-1;
cout<<g[n][x].val<<endl;
}
然后再加入最优性。注意到对于一个
同理,
int n;
int a[3000005];
int s;
int mnm[3000005],mxm[3000005];
struct structures{
int l,r,val;
};
#define vs vector<structures>
vs f[3000005],g[3000005];
int m;
int qlen(){
int cur=0,l=0,r=0;
mnm[0]=0;
REP(i,0,n){
int L=max(0ll,-a[i]),R=min(s,s-a[i]);
if(max(L,l)<=min(r,R))l=max(l,L),r=min(r,R);
else l=L,r=R,++cur;
l+=a[i];r+=a[i];
l=max(l,0ll);r=min(r,s);
if(l>r)l=0,r=s,++cur;
mnm[i+1]=cur+i+1;
}
return cur+(r<s)+n;
}
void getdp_back(){
int cur=0,l=0,r=0;
mxm[n]=m;
for(int i=n-1;i>=0;--i){
int L=max(0ll,-a[i]),R=min(s,s-a[i]);
if(max(L,l)<=min(r,R))l=max(l,L),r=min(r,R);
else l=L,r=R,++cur;
l+=a[i];r+=a[i];
l=max(l,0ll);r=min(r,s);
if(l>r)l=0,r=s,++cur;
mxm[i]=m-n-cur+i;
}
}
vs mover(vs a,int x){
if(!x)return a;
vs b;
if(x>0)b.pb({0,x-1,0});
for(auto [l,r,val]:a){
int L=l+x,R=r+x;
L=max(L,0ll);R=min(R,s);
if(L<=R)b.pb({L,R,val});
}
if(x<0)b.pb({s+x+1,s,0});
return b;
}
vs operator -(vs a,vs b){
vector<int>t;
int x=0,y=0,n=a.size(),m=b.size();
while(x<n&&y<m){
if(a[x].l==b[y].l)t.pb(a[x].l),++x,++y;
else if(a[x].l<b[y].l)t.pb(a[x++].l);
else t.pb(b[y++].l);
}
while(x<n)t.pb(a[x++].l);
while(y<m)t.pb(b[y++].l);
n=t.size();t.pb(s+1);
vs c(n,{0,0,0});
REP(i,0,n)c[i].l=t[i],c[i].r=t[i+1]-1;
x=0;
for(auto [l,r,val]:a){
while(c[x].r<l)++x;
while(x<n&&c[x].l>=l&&c[x].r<=r)c[x].val+=val,++x;
}
x=0;
for(auto [l,r,val]:b){
while(c[x].r<l)++x;
while(x<n&&c[x].l>=l&&c[x].r<=r)c[x].val+=MOD-val,++x;
}
REP(i,0,n)c[i].val%=MOD;
a.clear();
int lstval=-1;
for(auto [l,r,val]:c){
if(val==lstval)a[a.size()-1].r=r;
else a.pb({l,r,lstval=val});
}
return a;
}
void Main() {
cin>>n;
REP(i,0,n)cin>>a[i];
s=accumulate(a,a+n,0ll);
m=qlen();getdp_back();
REP(i,0,n+1){
g[i].clear();
if(i)g[i]=vs(1,{0,s,0});
else{
g[i].pb({0,0,1});g[i].pb({1,s,0});
}
}
int xl=0,xr=0;
REP(i,0,m){
int lxl=xl,lxr=xr;
while(xl<=n&&mxm[xl]<i+1)++xl;
while(xr+1<=n&&mnm[xr+1]<=i+1)++xr;
for(int j=xr;j>=xl;--j){
++cnt;
int sum=0;
for(auto [l,r,val]:g[j])(sum+=(r-l+1)%MOD*val)%=MOD;
vs s1=j<n? mover(g[j],a[j]):vs(1,{0,s,0}),s2=j? mover(g[j-1],a[j-1]):vs(1,{0,s,0});
g[j]=s2-s1;
for(auto &l:g[j])(l.val+=sum)%=MOD;
}
REP(i,lxl,xl)g[i]=vs(1,{0,s,0});
}
int x=g[n].size()-1;
cout<<g[n][x].val<<endl;
}
很抱歉实力有限,当天的补救中我只能做到这了。当时是赶工到凌晨五点才写完的这个东西,第二天发现没人会更优的,做不到
如果大家有更好的想法欢迎沟通。