circle (容斥原理+数据结构)

题目大意:在一条直线上有2*n个点,点与点之间两两配对成n组。现在要你选出三组点对,使得这三组点对满足112233,122331,123123的其中一种形式,问方案数。 n105 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]。答案便是

C3nxf[x](n1f[x]g[x])xg[x](n1g[x])2 C n 3 − ∑ x f [ x ] ( n − 1 − f [ x ] − g [ x ] ) − ∑ x g [ x ] ( n − 1 − g [ x ] ) 2

时间复杂度 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值