题目大意:在一条直线上有2*n个点,点与点之间两两配对成n组。现在要你选出三组点对,使得这三组点对满足112233,122331,123123的其中一种形式,问方案数。 n≤105 n ≤ 10 5 。
题目分析:多年前的老坑,昨天晚上想填一下,发现还是不会做,而且我还是看不懂题解。懵逼了一整晚,最后翻出标程来看,终于看懂了做法。
从n个点对中选取3个点对,有 C3n C n 3 种方案,考虑减掉不合法的情况(因为合法的情况无法正面求解)。首先考虑三条线段两两不相交,情况分为以下四种:
下面的两种情况是不合法的。观察一下它们的共同特点,发现在这两种情况中,必定能找到有且仅有一条线段x,使得另外两条线段一条在x内部,一条在x外部。再YY一下就会发现:如果枚举一条线段x,用在它内部的线段数量乘以在它外部的线段数量,就能对应出三条线段两两不相交时的所有不合法情况。
接下来处理至少有一对线段相交的情况,不妨记相交的线段为a,b。考虑第三条线段c,它有可能与a,b均不相交,可能与其中的一条有交,或者两条都相交:
其中蓝色线代表c的可能位置,而右下角的情况便是我们想要的答案之一。如果单独讨论另外两种情况,c的位置繁多而复杂。但再观察一下就会发现:不合法的两种情况中,必定存在线段x,使得另外两条线段一条与x相交,另一条与x不相交。而且这样的x一定有两条!当c与a,b均不相交时,x便为a和b;当c与a相交时,x便为c和b。
综上,我们不妨用数据结构预处理出每条线段内部的线段个数f[i],以及与其相交的线段个数g[i]。答案便是
时间复杂度 O(nlog(n)) O ( n log ( n ) ) 。
这题的关键在于对各种情况进行归纳和简化,而不是分类讨论。我一开始思考的时候,总想着对各种情况进行讨论,然而发现算不出来,无从下手。虽然这题用到的容斥原理十分简单,就是线段关系之间的加减,并没有子集反演看起来那么高大上,但它依旧是一道很妙的题。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=200100;
typedef long long LL;
int f[maxn];
int g[maxn];
int bit[maxn];
int a[maxn];
int n;
int Sum(int x)
{
int sum=0;
while (x)
{
sum+=bit[x];
x-=(x&(-x));
}
return sum;
}
int Query(int L,int R)
{
return Sum(R)-Sum(L-1);
}
void Add(int x)
{
while (x<=2*n)
{
bit[x]++;
x+=(x&(-x));
}
}
int main()
{
freopen("circle.in","r",stdin);
freopen("circle.out","w",stdout);
scanf("%d",&n);
for (int i=1; i<=n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x]=y;
a[y]=x;
}
for (int i=2*n; i>=1; i--)
if (i<a[i])
{
f[i]=Query(i,a[i]);
Add(a[i]);
}
for (int i=1; i<=2*n; i++) bit[i]=0;
for (int i=1; i<=2*n; i++)
if (i<a[i])
{
g[i]=Query(i,a[i]);
Add(a[i]);
}
for (int i=1; i<=2*n; i++) bit[i]=0;
for (int i=2*n; i>=1; i--)
if (a[i]<i)
{
g[ a[i] ]+=Query(a[i],i);
Add(a[i]);
}
LL ans=n*(n-1LL)*(n-2LL)/6LL;
LL temp=0;
for (int i=1; i<=2*n; i++)
if (i<a[i])
{
ans-=( (long long)f[i]*(long long)(n-1LL-f[i]-g[i]) );
temp+=( (long long)g[i]*(long long)(n-1LL-g[i]) );
}
temp>>=1;
ans-=temp;
printf("%I64d\n",ans);
return 0;
}