Codeforces Round #825 (Div. 2) C1,C2

 本文分享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,每个询问输出修改后合法的对数。

每一个修改是独立的(可以理解修改后又回到原数组),修改都是相对于原数组的修改。

思路:

按照上一题的解法二,原数组答案为\frac{n(n+1)}{2}-\sum_{i=1}^{n}f[i]

现在考虑修改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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值