JZOJ5803.【2018.8.12省选模拟】girls(三元环)

49 篇文章 0 订阅
32 篇文章 1 订阅

PROBLEM

有0至n-1个元素,给出m对元素的冲突,给出A,B,C,定义一个满足i<j<k的三元组(i,j,k)的贡献为A∗i+B∗j+C∗k,求所有没有冲突的三元组的贡献和

SOLUTION

考虑容斥,设F(i)表示每个三元组考虑i组冲突的总和

Ans=F(0)−F(1)+F(2)−F(3)

没有冲突的每个元素分别为A,B,C时独立计算次数

一条冲突的枚举冲突,讨论这两个端点的前后情况

两条冲突的同理同样是讨论。

考虑三条冲突在一个三元组里面,如果把每一个冲突看成一个环的话,那么就是对每一个三元环进行计数。问题在于三元环。

有两种方法。

  1. 第一种:将点根据度数是否小于sqrt(m)分成两类,小于sqrt(m)的点将其所有的连边暴力,时间为sqrt(m)^ 3;对于另一类点,直接三次方暴力只在这一类里的点,由于这里的点数不会超过sqrt(m),所以总的复杂度也不会超过sqrt(m)^ 3.因此总的复杂度不超过sqrt(m)^3。但由于是双向边,常数巨大。

  2. 第二种更优的做法: 将无向边定向为有向边,根据度数小的点连向度数大的点、其次是编号小的点连向编号大的点,可以发现这是一个有向无环图,直接枚举点,暴力枚举两条边并判断这两个点是否联通(hash、set或map)即可。并且这种方法不用去重,因为有向边只能从一个点出发计算,所以十分合适这题的打法,真是妙啊妙。

    考虑证明这种做法的正确性与时间复杂度。

    如果有一个长度为3的环,是不能够从一个点扫描到的。由于连向的点的度数不减,那么回到自己后度数不变,即说明这个环的度数相等,但我们又有编号小的点连向编号大的点,所以在这种情况下一定不会有环。

    长度为3的非环,所以有两条边共了一个起点,可以考虑到这种情况。

    因为每个点连向的点的度数比它大,所以度数不超过sqrt(n)。均摊复杂度不会超过m*sqrt(n),并不会证这种方法QwQ

  3. 还有另一种枚举的方法,时间复杂度可证:

    枚举点,将其通往的所有点打标记,再枚举通往的点,再暴力枚举这个点的出边是否有通往已标记的点上的。打标记为O(m),枚举每个通往的点总数为边数m,再枚举出边sqrt(n),总复杂度O(m*sqrt(n))

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define maxn 400005
#define ll unsigned long long
using namespace std;

int n,m,x,y,z,du[maxn],a[maxn][2],tot,node[maxn];
int em,e[maxn],nx[maxn],ls[maxn],r[3];
ll A,B,C,ans,i,j,k,len1,len2,s;
ll num[maxn],sum[maxn],pc[maxn],ps[maxn];
vector<int> to[maxn];
void insert(int x,int y){em++;e[em]=y;nx[em]=ls[x];ls[x]=em;}
ll que(ll x){if (x<=0) return 0; else return x*(x+1)/2;}

const ll p1=998244353,p2=10007,mo=3000007;
int hx[mo],p[mo][2];
void link(int x,int y){
	if (x>y) swap(x,y);
	ll s=(x*p1+y*p2)%mo;
	while (hx[s]&&(p[s][0]!=x||p[s][1]!=y)) 
		{s++; if (s==mo) s-=mo;}
	hx[s]=1,p[s][0]=x,p[s][1]=y;
}
int pd(int x,int y){
	if (x>y) swap(x,y);
	ll s=(x*p1+y*p2)%mo;
	while (hx[s]&&(p[s][0]!=x||p[s][1]!=y))
		{s++; if (s==mo) s-=mo;}
	return hx[s];
}

int main(){
	freopen("girls.in","r",stdin);
	freopen("girls.out","w",stdout);
	scanf("%d%d",&n,&m);
	scanf("%lld%lld%lld",&A,&B,&C);
	for(i=0;i<n;i++){
		len1=i,len2=n-i-1;
		ans+=A*i*((len2-1)*len2/2);
		ans+=B*i*len1*len2;
		ans+=C*i*((len1-1)*len1/2);
	}
	for(i=1;i<=m;i++) {
		scanf("%d%d",&x,&y);
		if (x>y) swap(x,y);
		a[i][0]=x,a[i][1]=y;
		num[y]++,sum[y]+=x;
		to[y].push_back(x);
		du[x]++,du[y]++;
		ans-=(A*x+B*y)*(n-y-1)+(que(n-1)-que(y))*C;
		ans-=(A*x+C*y)*(y-x-1)+(que(y-1)-que(x))*B;
		ans-=(B*x+C*y)*x+que(x-1)*A;
	}
	
    for(i=0;i<n;i++) sort(to[i].begin(),to[i].end());
	for(i=0;i<n;i++){
		ans+=num[i]*(num[i]-1)/2*i*C;
		for(j=0;j<to[i].size();j++){
			x=to[i][j];
			ans+=(num[i]-j-1)*to[i][j]*A;
			ans+=j*to[i][j]*B;
			
			ans+=num[to[i][j]]*(B*to[i][j]+C*i);
			ans+=sum[to[i][j]]*A;
			
			ans+=pc[to[i][j]]*(A*to[i][j]+C*i);
			ans+=ps[to[i][j]]*B;
			
			pc[to[i][j]]++,ps[to[i][j]]+=i;
		}
	}
	
	for(i=1;i<=m;i++){
		x=a[i][0],y=a[i][1];
		if (du[x]<du[y]) insert(x,y); else 
		if (du[x]>du[y]) insert(y,x); else 
			insert(x,y);
		link(x,y);
	}
	
	for(x=0;x<n;x++){
		tot=0;
		for(i=ls[x];i;i=nx[i]) node[++tot]=e[i];
		for(i=1;i<tot;i++) for(j=i+1;j<=tot;j++){
			r[0]=x,r[1]=node[i],r[2]=node[j];
			if (!pd(r[1],r[2])) continue;
			sort(r,r+3);
			ans-=r[0]*A+r[1]*B+r[2]*C;
		}
	}
	printf("%llu",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值