【计数】CF283E

博客探讨了如何解决CF283E问题,采用从图论的角度出发,利用计数和图的性质。文章首先介绍了问题的思路,指出问题本质上是一个计数问题,涉及图论和翻转操作。接着,作者提出了从三元组总数减去非三元环的数量来求解,并详细解释了如何计算这两个部分。在效率方面,通过引入线段树以优化O(nm)的时间复杂度。最后,博主展示了如何使用线段树进行状态维护和区间异或操作来实现解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

scanf毁我青春呀!!!!

思路

一眼显然计数,二眼显然图论(这显然是一张竞赛图),看到翻转想DS。

先考虑怎样计数。正向思考发现很难维护。

考虑倒着找答案——先算出所有三元组的个数,再减去非三元环的情况数

所有三元组数量很容易算,就是 Cn3C_n^3Cn3

注意到非三元环有一个性质,有且仅有一个点出度为2。

那就记 did_idi 表示 iii 号点的出度,非三元环答案即为 ∑i=1nCdi2\sum^{n}_{i=1}C_{d_i}^2i=1nCdi2。因为从每个点所连的点中选两个,这三个点必然不在一个环,并且有且仅有这一种情况。

于是我们得到了 O(nm)\mathcal{O(nm)}O(nm)的做法,但显然不够,主要问题在于处理翻边的时候不能快速处理。于是考虑用DS来维护。

发现我们关心的东西并不是那些点被当前点连了边,是有多少点连了边。

发现它满足可加性,于是想到线段树。

好像二维线段树也行,但实际上我们只需要普通线段树就可以了。

先考虑对单点如何维护。设当前点为 uuu,我们要维护一个01串 SSS

SiS_iSi表示 uuuiii 是否有边。那么修改操作显然是异或一下即可,答案统计就是区间和。

然后考虑如何通过上一个点的答案来算当前点的答案,可以再做一次上一个的操作把上次操作抵消了,再把这个点本来该做的操作再做一遍,最后把答案统计一边即可。

代码

#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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值