本文分享cf815(div2)的两个C题,其中C1题包括比赛时想到的线段树做法和官方做法。
C1. Good Subarrays (Easy Version)
题目链接:Problem - C1 - Codeforces
题目大意:
t(1≤t≤2⋅10e5)组测试,每一组给定长度为n(1≤n≤2⋅10e5)的数组a (1≤ai≤n)。
对于一个长度为m的数组b,如果满足 bi≥i(1≤i≤m). 则为合法的数组。
现在需要从a数组截取一段 (l,r) (1≤l≤r≤n) 构成新的数组,问使得这个数组和法的选取(l,r)对数有多少个。
思路:
解法一:
从前往后遍历,考虑a[i]作为右端点,需要找到其最大可以扩展到的左端点L。不考虑前面的约束下,L=max(1,i-(a[i]-1)),对答案的贡献为i-L
如图:
可以看到第2段并不合法,下面讨论如何找到正确的左端点。
对于i点,假设最大的左端点可以扩展到L=max(1,i-(a[i]-1)),对于L之间的一个数j,设其左端点最小为l,则有如下两种情况。
1.l<=L.
这种情况j也是合法的,因为l~j的线段a[j]的下标为j-l+1,L~j中j的下标为j-L+1<=j-l+1,既然前者合法,说明j-l+1=a[j],这样L~j也合法。
2.l<L
和上面相反,因为l已经是满足j的最左边了,L在l左边会造成j不合法。所以至少要将L移动到l。
基于这个思路,在比赛的时候无脑地想到了用线段树维护区间最大值。只需要预处理出f[i]数组代表第i个点的最左边,后面只需要查询[f[i],i]的最大值就好了。这样第i个点的贡献就是i-query(1,f[i],i)
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#define pii pair<int,int>
#define pll pair<LL,LL>
#define pil pair<int,LL>
#define pli pair<LL,int>
#define se second
#define fi first
#define endl '\n'
#define rep(i,a,b) for (register int i=a;i<b;++i)
#define per(i,a,b) for (register int i=a;i>b;--i)
typedef long long LL;
using namespace std;
const LL MOD=998244353;
const int N=2e5+10;
int a[N],f[N];
struct tree{
int l,r,v;
}T[N*4];
void up(int p)
{
T[p].v=max(T[p<<1].v,T[p<<1|1].v);
}
void build(int p,int l,int r)
{
T[p]={l,r};
if(l==r){
T[p].v=f[l];
return;
}
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
up(p);
}
int query(int p,int l,int r)
{
if(T[p].l>=l&&T[p].r<=r) return T[p].v;
int v=0;
if(T[p<<1].r>=l) v=max(v,query(p<<1,l,r));
if(T[p<<1|1].l<=r) v=max(v,query(p<<1|1,l,r));
return v;
}
void solve()
{
int n;
cin>>n;
rep(i,1,n+1) cin>>a[i];
LL ans=n;
f[1]=1;
rep(i,2,n+1) f[i]=max(1,i-(a[i]-1));
build(1,1,n);
rep(i,2,n+1){
ans+=i-query(1,f[i],i);
}
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}
解法二:
还是上面的思路,先设第i个点对答案的贡献为i,再减去多余的贡献就可以了。
和上面的原理一样,只不过这次直接把L设置为1,那么l就应该是前面j-a[j]中的最大值。
所以从前往后遍历,维护到当前i-a[i]中的最大值r,多余的值就是r。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#define pii pair<int,int>
#define pll pair<LL,LL>
#define pil pair<int,LL>
#define pli pair<LL,int>
#define se second
#define fi first
#define endl '\n'
#define rep(i,a,b) for (register int i=a;i<b;++i)
#define per(i,a,b) for (register int i=a;i>b;--i)
typedef long long LL;
using namespace std;
const LL MOD=998244353;
const int N=2e5+10;
//int a[N],b[N];
void solve()
{
int n,x,r=0;
cin>>n;
LL ans=0;
rep(i,1,n+1){
cin>>x;
r=max(r,i-x);
ans+=i-r;
}
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}
C2. Good Subarrays (Hard Version)
题目链接:Problem - C2 - Codeforces
题目大意:
在上一题的基础上,只有一组测试数据,给定a数组,有 q(1≤q≤2⋅10e5) 个询问,每一个询问将a[p]的值改为x,每个询问输出修改后合法的对数。
每一个修改是独立的(可以理解修改后又回到原数组),修改都是相对于原数组的修改。
思路:
按照上一题的解法二,原数组答案为
现在考虑修改a[p]=x对于f数组的影响,首先f[p]应更新的值记为nw = max(f[p-1] , p-x),则可以分为以下三种情况:
(1)nw = f[p],更新后f[p]不变,所以也不会影响后面的值,直接输出答案即可。
(2)nw > f[p],记r为f[p+1]~f[n]中第一个>=nw的值的位置,可以把f分为三段:
①1~p-1;
②p~r-1;
③r~n;
显然①段和原来f[i]是一样的;对于②段f[i]都满足f[i]<nw,故全部被更新为nw;③段f[i]满足f[i]>=nw,所以也不会受影响。
用s数组维护f[i]的前缀和,新的 Σf 可以为:①s[p-1];②(r-p)*nw;③s[n]-s[r-1]
(3)nw < f[p],这时候无法通过f数组找到nw更新的区间。对此我们再维护一个g数组,g[i]代表前i个i-a[i]第二大的值。记r1为在g中p+1~n第一个>=f[p]的值的位置,记r2为g中p~n第一个>=nw的值的位置。r1,r2把f分为四段。
①1~p-1:前面是不受影响的,故值为s[p-1]
②p~r2-1:这一段中nw<f[i],nw>g[i]。所以在修改后全部被更新为nw。故值为(r2-p)*nw.
(这里相当于在这段区间用nw计算新的值的时候,nw位于i-a[i]第一大值和第二大值之间,从p开始,可以理解为当前nw替换了原来的最大值,所以考虑其次大值。)
③r2~r1-1:这一段中nw<=g[i],但g[i]<f[p],所以这一段的值更新为g[r2]~g[r1]-1。用sg数组维护g数组的前缀和,这一段值可以表示为sg[r1-1]-sg[r2-1]
(相当于这里还没有回到最大值的状态,g[i]作为当前的最大值)
④r1~n:这一段中g[i]>=f[p],说明右回到了f[i]的状态,故值为s[n]-s[r1-1]
(相当于这里g[i]作为当前的次大值,f[i]作为当前最大值)
新的 Σf 可以表示为s[p-1]+(r2-p)*nw+sg[r1-1]-sg[r2-1]+s[n]-s[r1-1]
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#define pii pair<int,int>
#define pll pair<LL,LL>
#define pil pair<int,LL>
#define pli pair<LL,int>
#define se second
#define fi first
#define endl '\n'
#define rep(i,a,b) for (register int i=a;i<b;++i)
#define per(i,a,b) for (register int i=a;i>b;--i)
typedef long long LL;
using namespace std;
const LL MOD=998244353;
const int N=2e5+10;
int a[N],f[N],g[N];
LL s[N],sg[N];
void solve()
{
int n,q;
cin>>n;
rep(i,1,n+1) cin>>a[i];
LL ans=1ll*n*(n+1)/2;
rep(i,1,n+1){
g[i]=min(f[i-1],i-a[i]),g[i]=max(g[i],g[i-1]);
sg[i]=g[i]+sg[i-1];
f[i]=max(f[i-1],i-a[i]);
s[i]=f[i]+s[i-1];
}
cin>>q;
while(q--){
int p,x;
cin>>p>>x;
int nw=max(f[p-1],p-x);
if(nw>f[p]){
int r=lower_bound(f+p+1,f+1+n,nw)-f;
cout<<ans-s[p-1]-1ll*(r-p)*nw-(s[n]-s[r-1])<<endl;
}
else if(nw<f[p]){
int r1=lower_bound(g+p+1,g+1+n,f[p])-g,r2=lower_bound(g+p,g+1+n,nw)-g;
cout<<ans-s[p-1]-1ll*(r2-p)*nw-(sg[r1-1]-sg[r2-1])-(s[n]-s[r1-1])<<endl;
}
else cout<<ans-s[n]<<endl;
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _=1;
//cin>>_;
while(_--){
solve();
}
return 0;
}