scanf
毁我青春呀!!!!
思路
一眼显然计数,二眼显然图论(这显然是一张竞赛图),看到翻转想DS。
先考虑怎样计数。正向思考发现很难维护。
考虑倒着找答案——先算出所有三元组的个数,再减去非三元环的情况数
所有三元组数量很容易算,就是 Cn3C_n^3Cn3。
注意到非三元环有一个性质,有且仅有一个点出度为2。
那就记 did_idi 表示 iii 号点的出度,非三元环答案即为 ∑i=1nCdi2\sum^{n}_{i=1}C_{d_i}^2∑i=1nCdi2。因为从每个点所连的点中选两个,这三个点必然不在一个环,并且有且仅有这一种情况。
于是我们得到了 O(nm)\mathcal{O(nm)}O(nm)的做法,但显然不够,主要问题在于处理翻边的时候不能快速处理。于是考虑用DS来维护。
发现我们关心的东西并不是那些点被当前点连了边,是有多少点连了边。
发现它满足可加性,于是想到线段树。
好像二维线段树也行,但实际上我们只需要普通线段树就可以了。
先考虑对单点如何维护。设当前点为 uuu,我们要维护一个01串 SSS。
SiS_iSi表示 uuu 到 iii 是否有边。那么修改操作显然是异或一下即可,答案统计就是区间和。
然后考虑如何通过上一个点的答案来算当前点的答案,可以再做一次上一个的操作把上次操作抵消了,再把这个点本来该做的操作再做一遍,最后把答案统计一边即可。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n , m , s[N] , ans;
struct nod{
int l , r;
};
vector <nod> v1[N] , v2[N];
struct node{
int p , l , r , sum , tag;
}tr[N << 2];
int ls(int p)
{
return p << 1;
}
int rs(int p)
{
return p << 1 | 1;
}
void build(int p , int l , int r)
{
tr[p].l = l;tr[p].r = r;
if(l == r)return ;
int mid = (l + r) >> 1;
build(ls(p) , l , mid);
build(rs(p) , mid + 1 , r);
}
void push_up(int p)
{
tr[p].sum = tr[ls(p)].sum + tr[rs(p)].sum;
}
void push_down(int p)
{
if(tr[p].tag)
{
tr[ls(p)].tag ^= 1;tr[rs(p)].tag ^= 1;
tr[p].tag = 0;
int mid = (tr[p].l + tr[p].r) >> 1;
tr[ls(p)].sum = mid - tr[p].l + 1 - tr[ls(p)].sum;
tr[rs(p)].sum = tr[p].r - mid - tr[rs(p)].sum;
}
}
void modify(int p , int l , int r , int nl , int nr)
{
if(l >= nl && r <= nr)
{
tr[p].tag ^= 1;
tr[p].sum = r - l + 1 - tr[p].sum;
return ;
}
push_down(p);
int mid = (l + r) >> 1;
if(nl <= mid)modify(ls(p) , l , mid , nl , nr);
if(mid < nr)modify(rs(p) , mid + 1 , r , nl , nr);
push_up(p);
}
int query(int p , int l , int r , int nl , int nr)
{
int res = 0;
if(l >= nl && r <= nr)
{
return tr[p].sum;
}
push_down(p);
int mid = (l + r) >> 1;
if(nl <= mid)res += query(ls(p) , l , mid , nl , nr);
if(mid < nr)res += query(rs(p) , mid + 1 , r , nl , nr);
// push_up(p);
return res;
}
signed main()
{
cin >> n >> m;
ans = n * (n - 1) * (n - 2) / 6;
for(int i = 1;i <= n;i++)cin >> s[i];
sort(s + 1 , s + 1 + n);
for(int i = 1;i <= m;i++)
{
int l , r;
scanf("%lld%lld",&l,&r);
if(l > s[n] || r < s[1])continue;
l = lower_bound(s + 1 , s + 1 + n , l) - s;
r = upper_bound(s + 1 , s + 1 + n , r) - s - 1;
v1[l].push_back((nod){l , r});
v2[r + 1].push_back((nod){l , r});
}
build(1 , 1 , n);
for(int i = 1;i <= n;i++)
{
for(int j = 0;j < (int)v1[i].size();j++)modify(1 , 1 , n , v1[i][j].l , v1[i][j].r);
for(int j = 0;j < (int)v2[i].size();j++)modify(1 , 1 , n , v2[i][j].l , v2[i][j].r);
int p = 0;
if(i > 1)p += i - 1 - query(1 , 1 , n , 1 , i - 1);
if(i < n)p += query(1 , 1 , n , i + 1 , n);
if(p > 1)ans -= p * (p - 1) / 2;
}
cout << ans;
return 0;
}