【SMOJ1699】圆与点与线段
题目描述
有一个圆,圆周上按顺时针方向给出个点。第个点的颜色是,其中数据保证,而且每种不同的颜色有且只有两个点。这意味着有两个颜色是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;
}
方法三:运用差分的思想。(由于本文作者智商有限,暂时还没想出来……欢迎各位大佬在讨论区里指教)