AGC054F - Decrement
有两个长度分别为\(n\)和\(n-1\)的数组\(A,B\)。
每次可以选择\(l<r\),操作\(A_l,A_r\)和\(B_l,B_{l+1},\dots,B_{r-1}\),将其减一。要求操作之后不能出现负数。
问在操作次数最多的前提下,操作之后不同的数组\(a\)的方案数。
\(n\le 2*10^5\)
假设\(A_i,B_i\)最终各减了\(a_i,b_i\)。考虑\(a_i,b_i\)能够减完的充要条件:
令\(b_0=b_n=0\)
- \(2|(a_i+b_{i-1}+b_i)\)
- \(\max(a_i,b_{i-1},b_i)\le \frac{a_i+b_{i-1}+b_i}{2}\)
证明:
必要性:以\(a_i,b_{i-1},b_i\)的视角考虑,每次与其有关的操作一定是选择其中两个减一。
充分性:归纳。假如操作了\(l,r\),必须要满足:\(\forall i\in(l,r),a_i<b_{i-1}+b_i\)和\(b_{l-1}<a_l+b_l,b_r<a_r+b_{r-1}\)。如果将\(a_i=b_{i-1}+b_i\)的点提取出来,任取相邻两个操作,就可以满足。(\(1\)和\(n\)一定满足,所以至少两个)
先调整\(B_i\)和\(A_i\)。如果\(B_{i-1}>A_i+B_i\),则将其调整为\(B_{i-1}\);\(B_i\)类似。搞完\(B\)之后,如果出现\(A_i\ge B_{i-1}+B_i\),可以将问题以\(i\)为分界分开,对于左边假装\(A_i=B_{i-1}\),对于右边假装\(A_i=B_{i}\)。
然后要最大化\(\sum a_i\)。对于一个子问题,数下\(t_i=(A_i+B_{i-1}+B_i)\mod 2=1\)的个数\(c\),如果\(2|c\),把相邻的\(t_i=1\)的\(i\)两两配对,每对之间的\(B\)减一,此时\(\sum A_i\)取到\(\sum A_i\);否则至多取到\(\sum A_i-1\),如果钦定\(a_1=A_1-1\),用类似的方法也可以达到这个界。
算方案:如果\(2|c\)则唯一;否则要对每个\(i\)判断一下能否取\(a_i=A_i-1\),这个东西做个前缀和后缀的DP,合并起来即可。具体:设\(f_{i}\)表示\(a_1,\dots,a_i\)取\(A_1,\dots,A_i\),此时\(b_i\)可行的取值(区间+奇偶性),另一边类似。
题解说时间\(O(n)\),然而我不知道前面调整\(B\)的那部分怎么\(O(n)\)做,我是直接跑最短路的……
using namespace std;
#include <bits/stdc++.h>
const int N=200005,mo=998244353;
typedef long long ll;
#define fi first
#define se second
#define mp make_pair
int n;
int a[N],b[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
void init(){
for (int i=0;i<=n;++i)
q.push(mp(b[i],i));
while (!q.empty()){
int x=q.top().se,s=q.top().fi;
q.pop();
if (s>b[x]) continue;
if (x>0 && a[x]+b[x]<b[x-1]){
b[x-1]=a[x]+b[x];
q.push(mp(b[x-1],x-1));
}
if (x<n && a[x+1]+b[x]<b[x+1]){
b[x+1]=a[x+1]+b[x];
q.push(mp(b[x+1],x+1));
}
}
}
struct status{int l,r,c;} f[N],g[N];
status merge(status x,status y){
status z;
z.c=x.c^y.c;
z.r=x.r+y.r;
if (max(x.l,y.l)<=min(x.r,y.r))
z.l=z.c;
else
z.l=min(abs(x.l-y.r),abs(x.r-y.l));
return z;
}
ll work(int l,int r){
if (l==r) return 1;
int c=0;
c^=a[l]+b[l]&1;
c^=a[r]+b[r-1]&1;
for (int i=l+1;i<r;++i)
c^=a[i]+b[i-1]+b[i]&1;
if (c^1)
return 1;
f[l-1]={0,0,0};
for (int i=l;i<r;++i){
f[i]=merge(f[i-1],(status){a[i],a[i],a[i]&1});
f[i].r=min(f[i].r,b[i]);
if (f[i].r&1^f[i].c)
f[i].r--;
}
g[r+1]={0,0,0};
for (int i=r;i>l;--i){
g[i]=merge(g[i+1],(status){a[i],a[i],a[i]&1});
g[i].r=min(g[i].r,b[i-1]);
if (g[i].r&1^g[i].c)
g[i].r--;
}
/*
for (int i=l;i<=r;++i){
printf("(%d,%d,%d) (%d,%d,%d)\n",f[i].l,f[i].r,f[i].c,g[i].l,g[i].r,g[i].c);
}*/
ll cnt=0;
for (int i=l;i<=r;++i){
status t=merge(f[i-1],g[i+1]);
if (t.c==(a[i]-1&1) && (t.l<=a[i]-1 && a[i]-1<=t.r)){
//printf("%d\n",i);
cnt++;
}
}
return cnt;
}
int main(){
freopen("in.txt","r",stdin);
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
for (int i=1;i<n;++i)
scanf("%d",&b[i]);
init();
ll ans=1;
int lst=1;
for (int i=1;i<=n;++i){
if (a[i]>=b[i-1]+b[i]){
a[i]=b[i-1];
ans=ans*work(lst,i)%mo;
lst=i;
a[i]=b[i];
}
}
ans=ans*work(lst,n)%mo;
printf("%lld\n",ans);
return 0;
}