Codeforces 1452E Two Editorials(树状数组)

Codeforces 1452E Two Editorials(树状数组)

题目链接:E. Two Editorials
题意:

给定长度为 n n n的区域,和 m m m条线段,让你选出两个长度为 k k k的区间(允许相交),每条线段的贡献为和两个区域的相交部分中较大的那个,问怎么选择两个区域使得总贡献最大,输出最大贡献

数据范围 1 ≤ n , m ≤ 2000 , 1 ≤ k ≤ n , 1 ≤ l i ≤ r i ≤ n . 1≤n,m≤2000, 1≤k≤n ,1≤li \leq ri≤n. 1n,m2000,1kn,1lirin.

题目分析:首先,对于每个线段,我们令其中心点为 m i d mid mid,对于每个线段,他们一定选择两个区域中中心离自己更近的一个,因为两个区域登长,离自己中心近的点一定能产生更多贡献。

深入分析:知道了上面的的结论,我们可以把所有线段按中心点升序排序,并且观察数据范围,只有2e3,我们可以暴力枚举两个区域的左端点,在第一个区间确定的情况下,用类似双指针的技巧,初始时把所有线段都划分到第二个区间(因为是按线段中心点升序排序的),每次判断下当前指针所指向线段中心点是否离前面一个区间中心点更近,如果是就划分到前面区间去,计算贡献时可以使用树状数组求区间和。

具体实现:我们先枚举第一个区间左端点时,考虑在固定第一个区间左端点时,计算所有第二个区间左端点的贡献。初始时把所有线段划分给第二个区间,这里使用数组数组(或者线段树)记录每个点被线段覆盖此时(即每个点对应贡献),在枚举第二个区间左端点时,考虑下当前指针所指线段中心点是否离第一个区间中心点更近,是的话根据“题目分析”中结论,该线段划分到第一个区间的贡献中去,因为第一个区间此时位置固定,直接计算贡献即可,同时删除该线段在第二个区间的贡献,这样就保证了剩下的区间都是属于第二个区间的贡献,用区间查询操作即可快速求贡献,两个区间贡献加一起就是当前情况下答案了。

注意事项:这里的中心点我们使用 l + r l+r l+r表示,避免了除法产生小数部分。
代码及注释

//
#include<iostream>
#include<stack>
#include<list>
#include<set>
#include<vector>
#include<algorithm>
#include<math.h>
#include<numeric>
#include<map>
#include<cstring>
#include<queue>
#include<iomanip>
#include<cmath>
#include<queue>
#include<fstream>
using namespace std;
typedef long long ll;
const ll inf=1e15;
const ll MAXN=2e3+100;
const ll mod=1e9+7;
const ll hash_p1=1610612741;
const ll hash_p2=805306457;
const ll hash_p3=402653189;
//-----------------------------------------------------------------------------------------------------------------*/
ll sum1[MAXN],sum2[MAXN],n,m;
void update(ll p, ll v){
	ll k=p;
	while(p<=n){
		sum1[p]+=v;
		sum2[p]+=k*v;
		p+=p&-p;
	}
}
void pr_update(ll l,ll r,ll x){
	update(l,x);
	update(r+1,-x);
}
ll ask(ll p){
	ll k=p,ans=0;
	while(p){
		ans+=(k+1)*sum1[p]-sum2[p];
		p-=p&-p;
	}
	return ans;
}
ll pr_ask(ll l,ll r){
	return ask(r)-ask(l-1);
}
struct elemt{
	int l,r;
};
elemt a[2010];
bool comp(elemt v1,elemt v2){
	return v1.l+v1.r<v2.l+v2.r;
}
int get_val(int l,int r,int le,int ri){//获取二者相交部分长度
	if(r<le){          			//  -----
		return 0;	   			//         -----
	}
	else if(r<=ri){             //  ----        ----
		return r-max(l,le)+1;   //    ----    ---------
	}
	else if(l<=ri){             //    ----     -------
 		return ri-max(l,le)+1;  //  ----        ----
	}
	else{					    //       ----
		return 0;               // ----
	}
}
int main(){
/*cout<<setiosflags(ios::fixed)<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(不含整数部分)*/
/*cout<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(含整数部分)*/
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);//同步流
	int k;
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++){
		cin>>a[i].l>>a[i].r;
	}
	sort(a+1,a+m+1,comp);//按l+r(中心点)排序
	ll ans=0;//存储答案
	for(int i=1;i+k-1<=n;i++){//枚举的第一段的开始点
		int res=0;//当前枚举的第一段对应答案
		int l=1;
		memset(sum1,0,sizeof(sum1));
		memset(sum2,0,sizeof(sum2));//清空树状数组
		for(int j=1;j<=m;j++){//刚开始全划分给第二段
			pr_update(a[j].l,a[j].r,1);//增加第二段对应贡献
		}
		if(k==n){//特判
			ans=pr_ask(1,n);
		}
		for(int j=i+1;j+k-1<=n;j++){//枚举的第二段的开始点
			while(l<=m&&abs((j+j+k-1)-(a[l].l+a[l].r))>abs((i+i+k-1)-(a[l].l+a[l].r))){//当前段离第一段更近,划分到第一段
				pr_update(a[l].l,a[l].r,-1);//减少第二段对应贡献
				res+=get_val(i,i+k-1,a[l].l,a[l].r);//第一段加上对应贡献
				//cout<<"i="<<i<<" i+k-1="<<i+k-1<<" al="<<a[l].l<<" ar="<<a[l].r<<" v="<<get_val(i,i+k-1,a[l].l,a[l].r)<<endl;
				l++;
			}
			//cout<<"i="<<i<<" j="<<j<<" l="<<l<<endl;
			ans=max(ans,res+pr_ask(j,j+k-1));//更新答案
		}
	}
	cout<<ans<<endl;
return 0;
}
//
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值