树状数组的应用之把问题巧妙转换为树状数组的问题(【SMOJ】圆与点与线段解题报告)

题目描述

有一个圆,圆周上按顺时针方向给出个点。第个点的颜色是,其中数据保证,而且每种不同的颜色有且只有两个点。这意味着有两个颜色是1的点,有两个颜色是2的点,有两个颜色是3的点,….有两个颜色是的点。不存在位置重叠的点。

颜色相同的两个点之间连一条边(线段)。现在的问题是:有多少对边是交叉的?

输入格式 1699.in

第一行,一个整数。。

第二行,个整数,第个整数是表示第个点的颜色。

输出格式 1699.out

一个整数。

输入样例 1699.in

4

3 2 4 4 1 3 2 1

输出样例 1699.out

3

样例解释(下图)

 

边1与边2交叉;

边1与边3交叉;

边2与边3交叉。

题目分析:这一题一开始看还以为是什么数据结构大毒瘤,但是仔细一想,还是可以转换为树状数组的题目。



方法1:

首先,我们先给每一个点编一个号(如下图)。

 

第二步,我们把相同颜色的点连边。


我们可以发现连的边(u,v)分别为

1 6

2 7

3 4

5 8

我们让u<v

 

然后我们按照u排序。对于一条边(u,v),我们判断一条边是否和它相交就是那条边的右端点(也就是v)是否在u+1到v-1之间(想想为什么,嗯没错,应为u是升序的)。这里我们可以用树状数组来维护。

注意!!!这个方法必须是边连边边算节点个数的(想想为什么)。还有是拍完序后再连边。

 

步骤分析:


一开始啥都没有,不管它。


现在连了一条边了,不过看似好像还是没有什么用。


现在连了两条边,边2+1到7-1即3到6之间这一块出现了一个右端点6,所以答案加1。


现在又连了一条边,但是还是没有什么用对结果没有什么影响。


连上最后一条边了,边(5,8),6到7之间有两个右端点,所以答案加2。

 

最后答案就是1+2=3.

 

是不是很简单大笑手动划去

 

参考代码:


#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000005;
struct A{
	int u,v;
}a[MAXN];
int n,m,k,p[MAXN];
long long ans;
bool cmp(A a,A b){
	return a.u<b.u;
}
int tree[MAXN];
int lowbit(int x){
	return x&(-x);
}
int query(int x){
	int ret=0;
	for(int i=x;i;i-=lowbit(i)) ret+=tree[i];
	return ret; 
}
void update(int x,int y){
	for(int i=x;i<=2*n;i+=lowbit(i))	tree[i]+=y;
}
int main()
{
	freopen("1699.in","r",stdin);
	freopen("1699.out","w",stdout);
 	scanf("%d",&n);
 	for(int i=1;i<=2*n;i++){
 		scanf("%d",&k);
 		if(p[k]) m++,a[m].u=p[k],a[m].v=i;
 		p[k]=i;
	 }
	 sort(a+1,a+1+m,cmp);
	 for(int i=1;i<=m;i++){
	 	update(a[i].v,1);
	 	ans+=query(a[i].v-1)-query(a[i].u);
	 }
	 cout<<ans;
   return 0;
}

 

方法二:

这个方法是老师在上课的时候我想出来的,当时太兴奋打了个容斥原理,结果发错地方了,发给了老师。

废话不多说,来讲讲我的思路。

首先,连边的操作还是和方法一的一样。

不过不同的就是我的方法是先连好边,然后在计算交叉点。

对于一条边(u,v)如何计算和它交叉的边的个数呢?

首先我的思路是这样的,就计算u+1到v-1之间左端点的个数减去u+1到v-1之间右端点的个数(前提:上文也提到过,必须要满足u<v)。

但是如何解决计算重复的问题?

很简单,只需要计算完一条边之后把那条边删了就可以了(说白了就是把点删了)。

步骤分析

相信聪明的你们肯定不用分析都知道的了(其实是因为懒) 就是方法一的图片反过来而已。

参考代码:(这种方法跑得比第一种慢一点,其实也就慢0.004秒

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000005;
struct A{
	int u,v;
}a[MAXN];
int n,m,k,p[MAXN];
long long ans;
bool cmp(A a,A b){
	return a.u<b.u;
}

int tree_v[MAXN],tree_u[MAXN];
int lowbit(int x){
	return x&(-x);
}
int query_v(int x){
	int ret=0;
	for(int i=x;i;i-=lowbit(i)) ret+=tree_v[i];
	return ret; 
}
void update_v(int x,int y){
	for(int i=x;i<=2*n;i+=lowbit(i))	tree_v[i]+=y;
}

int query_u(int x){
	int ret=0;
	for(int i=x;i;i-=lowbit(i)) ret+=tree_u[i];
	return ret; 
}
void update_u(int x,int y){
	for(int i=x;i<=2*n;i+=lowbit(i))	tree_u[i]+=y;
}

int main()
{
	freopen("1699.in","r",stdin);
	freopen("1699.out","w",stdout);
 	scanf("%d",&n);
 	for(int i=1;i<=2*n;i++){
 		scanf("%d",&k);
 		if(p[k]) m++,a[m].u=p[k],a[m].v=i;
 		p[k]=i;
	 }
	 for(int i=1;i<=m;i++){
	 	update_v(a[i].v,1);	
	 	update_u(a[i].u,1);
	 }
	 for(int i=1;i<=m;i++){
	 	ans+=(query_u(a[i].v-1)-query_u(a[i].u))-(query_v(a[i].v-1)-query_v(a[i].u));
	 	update_v(a[i].v,-1);	
	 	update_u(a[i].u,-1);
	 }
	 cout<<ans;
   return 0;
}

方法三:运用差分的思想。(由于本文作者智商有限,暂时还没想出来……欢迎各位大佬在讨论区里指教)


posted @ 2018-05-13 15:20  lahlah  阅读(40)  评论(0)    收藏  举报