Greetings

Codeforces Round 918 (Div. 4)
F. Greetings
题目链接

题意:

给你n和n对l和r,保证l<r,且给出的所有lr两两不同。每个l位置上有个人,他们以相同的速度向r位置走,到达后停下,当两个人在同一个位置时会打一次招呼,问所有人停下时共打了几次招呼。

思路:

比赛时想的有点歪,实现上特别困难,最后居然还写出来。

先看两个人怎么才能打上招呼,不难发现只有l1 l2 r2 r1这种形式才能打上招呼,即1号的区间完全包含2号的区间时才会产生一次贡献。类似的可以推理出i号可以打招呼的人是i号区间里包含的完整的区间的个数,而i号对其他人的贡献是l在li左边,而r在ri的人数。

那么我们不妨从左到右枚举l,看现在有几个r在ri右边的人数(也就是r>ri),这就是i号的贡献,把这些人数累加起来就是答案。

那么如何实现呢?枚举l好说,拿l找r也也好说,那么怎么找r比ri大的人数呢,因为是动态维护单点修改区间查询,考虑树状数组,不过ai很大,需要离散化。

实际上,如果我们按l进行排序,看r,那么其实就是要找它前面比ri大的r,也就是求逆序对,所以我们把lr组合在一起,先按l排序,再按r找逆序对个数就行了,上面树状数组加离散化本质上也是在求逆序对。

我的想法是用栈,先把lr放在一起排序,然后依次查看,如果是左端点l就入栈,如果是右端点就把栈中相应位置的l标记一下,表示这个区间已经完整了,l理论上应该出栈了,同时cnt数组记录一下以li为最近的未完成结合的左端点时完成结合后的区间个数,当li和ri出栈的同时,假如li在栈中的位置是idx,栈顶是top那么显然idx和top之间的cnt之和就是li和ri结合后对答案的贡献

实现上左端点就直接入栈,对右端点就找到栈中的左端点位置idx,标记idx表示出栈,计算这一对的贡献,维护cnt(给cnt[idx]++),最后用过while循环对栈顶尝试出栈,如果被标记了就出栈,同时把cnt加给下一个栈顶

code:

赛时code:(初版,TLE3)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <cstring>
#include <set>
using namespace std;
typedef long long ll;
const int maxn=2e5;
 
int T,n;
map<int,int> mp;
 
pair<int,bool> a[maxn<<1];
 
//inline int getl(int r){
//	return lower_bound(a+1,a+(n<<1)+1,make_pair(mp[r],false))-a;
//}
 
 
int s[maxn],top;
ll cnt[maxn];
set<int> vis;
void p(){
	for(int i=1;i<=top;i++)
		cout<<s[i]<<" ";
	puts("");
	for(int i=1;i<=top;i++)
		cout<<cnt[i]<<" ";
	puts("");
//	puts("");
}
ll solve(){
	ll ans=0;
	top=0;
	vis.clear();
//	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=(n<<1);i++){
		if(!a[i].second){//左端点 
			s[++top]=a[i].first;
			cnt[top]=0;
		}
		else {
			int l=mp[a[i].first],idx=lower_bound(s+1,s+top+1,l)-s;
//			printf("%d %d\n",idx,s[idx]);
			for(int x=idx;x<=top;x++)ans+=cnt[x];
			cnt[idx]++;
			vis.insert(l);
		}
//		p();
		while(top && vis.count(s[top])){
			cnt[top-1]+=cnt[top];
			top--;
		}
//		p();
//		puts("");
		
	}
	return ans;
}
 
int main(){
	cin>>T;
//	T=1;
	while(T--){
		cin>>n;
		mp.clear();
		for(int i=1,x,y;i<=n;i++){
			cin>>x>>y;
			mp[x]=y;
			mp[y]=x;
			a[i]=make_pair(x,0);
			a[i+n]=make_pair(y,1);
		}
		sort(a+1,a+(n<<1)+1);
		
		ll ans=solve();
		cout<<ans<<endl;
	}
	return 0;
}
/*
5
-4 9
-2 5
3 4
6 7
8 10
 
*/

这个是树状数组优化的,1800ms:(树状数组优化就只优化了一个区间求和,也就是求idx到top区间cnt的和)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <cstring>
#include <set>
using namespace std;
typedef long long ll;
const int maxn=2e5;
 
int T,n;
map<int,int> mp;
 
pair<int,bool> a[maxn<<1];
 
//inline int getl(int r){
//	return lower_bound(a+1,a+(n<<1)+1,make_pair(mp[r],false))-a;
//}
 
 
 
int s[maxn],top;
ll cnt[maxn<<1];
set<int> vis;
 
inline int lowbit(int x){return x&(-x);}
void add(int i,ll x){
	if(!i)return;
	for(int p=i;p<=(n<<1);p+=lowbit(p))cnt[p]+=x;
}
ll gtpre(int i){
	ll ans=0;
	for(int p=i;p;p-=lowbit(p))ans+=cnt[p];
	return ans;
}
ll gt(int l,int r){
	return gtpre(r)-gtpre(l-1);
}
void p(){
	for(int i=1;i<=top;i++)
		cout<<s[i]<<" ";
	puts("");
	for(int i=1;i<=top;i++)
		cout<<gt(i,i)<<" ";
	puts("");
//	puts("");
}
ll solve(){
	ll ans=0;
	top=0;
	vis.clear();
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=(n<<1);i++){
		if(!a[i].second){//左端点 
			s[++top]=a[i].first;
			add(top,-gt(top,top));
		}
		else {
			int l=mp[a[i].first],idx=lower_bound(s+1,s+top+1,l)-s;
//			printf("%d %d\n",idx,s[idx]);
			ans+=gt(idx,top);
			add(idx,1);
			vis.insert(l);
		}
//		p();
		while(top && vis.count(s[top])){
			add(top-1,gt(top,top));
			top--;
		}
//		p();
//		cout<<"**"<<ans<<endl;
//		puts("");
		
	}
	return ans;
}
 
int main(){
	cin>>T;
//	T=1;
	while(T--){
		cin>>n;
		mp.clear();
		for(int i=1,x,y;i<=n;i++){
			cin>>x>>y;
			mp[x]=y;
			mp[y]=x;
			a[i]=make_pair(x,0);
			a[i+n]=make_pair(y,1);
		}
		sort(a+1,a+(n<<1)+1);
		
		ll ans=solve();
		cout<<ans<<endl;
	}
	return 0;
}
/*
5
-4 9
-2 5
3 4
6 7
8 10
 
*/

求逆序对,800ms:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;

int T,n;
pair<int,int> a[maxn];

int t[maxn];
ll gbsort(int L,int R){
	if(L>=R)return 0;
	int mid=(L+R)>>1,i=L,j=mid+1,k=L;
	ll ans=0;
	ans+=gbsort(L,mid)+gbsort(mid+1,R);
	while(i<=mid && j<=R){
		if(a[i].second<a[j].second){
			t[k++]=a[i++].second;
		}
		else {
			t[k++]=a[j++].second;
			ans+=mid-i+1;
		}
	}
	while(i<=mid)t[k++]=a[i++].second;
	while(j<=R)t[k++]=a[j++].second;
	for(k=L;k<=R;k++)
		a[k].second=t[k];
	return ans;
}

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i].first>>a[i].second;
		sort(a+1,a+n+1);
		
		cout<<gbsort(1,n)<<endl;
	}
	return 0;
} 
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值