本文主要是介绍CF297E JZOJ 4018.【雅礼联考DAY02】Magic,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
H y p e r l i n k Hyperlink Hyperlink
https://www.luogu.com.cn/problem/CF297E
D e s c r i p t i o n Description Description
在一个园上分布着 2 n 2n 2n个点,对应 n n n条弦
请求出有多少个无序的三元组,使得对应的三条弦可以通过距离的缩放中心对称
数据范围: n ≤ 1 0 5 n\leq 10^5 n≤105
S o l u t i o n Solution Solution
三条弦可能的情况有下列5种
答案即为第二种和第五种的组合,显然用容斥可以更好得到答案
所有的组合情况共为 C n 3 C_n^3 Cn3,我们直接当做 A n s = n ( n − 1 ) ( n − 2 ) / 6 Ans=n(n-1)(n-2)/6 Ans=n(n−1)(n−2)/6,然后减去1、3、4三种情况即可
第一种情况比较好计算,我们只需要找到第 i i i条弦左边不与它相交的弦的数量 x i x_i xi,以及右边不与它相交的弦的数量 y i y_i yi,答案即为 x i × y i x_i\times y_i xi×yi
既然左右两边不相交的弦的数量是 x i + y i x_i+y_i xi+yi,则与 i i i相交的弦即为所有的弦出去本身( n − 1 n-1 n−1)减去不想交的弦的数量,即 z i = n − 1 − ( x i + y i ) z_i=n-1-(x_i+y_i) zi=n−1−(xi+yi)
则第三第四种的总和即为 z i × ( x i + y i ) z_i\times(x_i+y_i) zi×(xi+yi)【相交的一个,不相交的两个】
则最终答案即为 A n s − ∑ i = 1 n ( x i y i + z i × ( x i + y i ) ) Ans-\sum_{i=1}^n (x_iy_i+z_i\times(x_i+y_i)) Ans−∑i=1n(xiyi+zi×(xi+yi))
在算法实现中,具体的流程与上述描述的略为不同,我们不是直接求出不相交的,而是通过树状数组维护相交的弦的数量,再通过容斥计算得出
首先将所有的弦按照右端点升序排序,这样可以保证后面的弦的右端点一定在前面的弦的右端点的后边
假设我们现在要求相交的弦的数量,即 z i z_i zi,定义与它相交的弦是 j j j,有两种情况
- j j j左端点在 i i i的左边,且 j j j的右端点在 i i i之间
- j j j的左端点在 i i i之间,且 j j j的右端点在 i i i右边
用 T p Tp Tp(一个区间修改,单点查询的树状数组)计算所有右端点在 i i i左侧的弦【一段区间】,表示这段区间有一条弦是可以作为 j j j的端点的,如果 p [ i ] . l p[i].l p[i].l在这段区间内,那么就会有贡献,我们查询 T p . a s k ( p [ i ] . l ) Tp.ask(p[i].l) Tp.ask(p[i].l)即可,由于我们维护的是右端点在 i i i左侧的弦,所以我们要做完 i i i之后将这条线更新进去,即 T p . a d d ( p [ i ] . l , 1 ) T p . a d d ( p [ i ] . r , − 1 ) Tp.add(p[i].l,1)\ \ \ \ \ Tp.add(p[i].r,-1) Tp.add(p[i].l,1) Tp.add(p[i].r,−1)
用 T a Ta Ta(一个单点修改,区间查询的树状数组)计算所有的右端点在 i i i右侧的左端点,因为我们已经排了序,所以我们先放入所有的左端点,在做每一条弦之前把这条弦删掉(因为它不可能在自己的右边),这样我们就知道了右端点在 i i i的右边的左端点的分布情况(即第二种),那么我们只需要知道在 p i . l ∼ p i . r p_i.l\sim p_i.r pi.l∼pi.r之间有多少个合法的左端点即可,即 T a . a s k ( p [ i ] . r ) − T a . a s k ( p [ i ] . l ) Ta.ask(p[i].r)-Ta.ask(p[i].l) Ta.ask(p[i].r)−Ta.ask(p[i].l)
于是我们求出了 z i z_i zi,现在考虑用它求 x i x_i xi和 y i y_i yi中的其中一个,就可以求出剩下那个,由于我们是按照右端点升序排序的,那么满足前面的右端点一定不会伸到后面去,所以 r r r已经在里面了,只需要找到 p [ i ] . l < p [ j ] . l < p [ i ] . r p[i].l<p[j].l<p[i].r p[i].l<p[j].l<p[i].r的【即所有左边的弦】减去所有相交的弦再除以2就可以得到 x i x_i xi
然后 y i = n − 1 − ( x i + z i ) y_i=n-1-(x_i+z_i) yi=n−1−(xi+zi)
直接统计即可
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
C o d e Code Code
#include<cctype>
#include<cstdio>
#include<algorithm>
#define LL long long
#define N 100010
using namespace std;int n;
struct node{int l,r;}p[N];
LL ans,x,y,z;
inline LL read()
{char c;LL d=1,f=0;while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;return d*f;
}
inline bool cmp(node x,node y){return x.r<y.r;}
struct sszz
{LL C[N*2];inline void add(int x,int v){for(;x<=2*n;x+=x&-x) C[x]+=v;return;}inline LL ask(int x){LL T=0;for(;x;x-=x&-x)T+=C[x];return T;}
}Ta,Tp;
signed main()
{n=read();for(register int i=1;i<=n;i++){p[i].l=read();p[i].r=read();if(p[i].l>p[i].r) swap(p[i].l,p[i].r);}sort(p+1,p+1+n,cmp);ans=(long long)n*(n-1)*(n-2)/6ll;for(register int i=1;i<=n;i++) Ta.add(p[i].l,1);for(register int i=1;i<=n;i++){Ta.add(p[i].l,-1);z=Tp.ask(p[i].l)+Ta.ask(p[i].r)-Ta.ask(p[i].l);x=(p[i].r-p[i].l-1-z)/2;y=n-1-x-z;ans-=x*y+z*(x+y)/2;Tp.add(p[i].l,1);Tp.add(p[i].r,-1);} printf("%lld",ans);
}
这篇关于CF297E JZOJ 4018.【雅礼联考DAY02】Magic的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!