把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ3498】[PA2009] Cakes(某黑科技)

点此看题面

大致题意: 对于所有三元环,求三个顶点的最大权值之和。

黑科技——三元环

印象里这个黑科技好像在谁讲课的时候介绍过,是个很巧妙但不常见的技巧。

考虑如何高效且不重不漏地枚举三元环。

我们统计每个点的度数,然后人为给无向边定向,从度数小的点向度数大的点连边(度数相同时从编号小向编号大连边)。

这样一来第一个好处就是,一张无向图被我们转化为了\(DAG\)

求解时,我们枚举每一个点\(i\),然后枚举每一个它连向的点\(j\)打上标记。接着再次枚举每一个它连向的点\(j\),并继续枚举\(j\)连向的点\(k\),如果\(k\)被打了标记,则\((i,j,k)\)必然是一个三元环。

正确性应该比较显然,复杂度证明可以用根号思想:

  • 如果\(deg_i\le\sqrt m\),复杂度显然是\(O(\sqrt m)\)的。
  • 如果\(deg_i>\sqrt m\),由于它连向的点度数一定大于\(deg_i\),也就大于\(\sqrt m\),最多只有\(\sqrt m\)个,复杂度也是\(O(\sqrt m)\)

因此总复杂度\(O(m\sqrt m)\)

此题做法

根据上面的黑科技,直接枚举每一个三元环,统计答案即可。

这题应该可以算是这种黑科技的板子题?

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 250000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,a[N+5],d[N+5],f[N+5],x[M+5],y[M+5],ee,lnk[N+5];struct edge {int to,nxt;}e[2*M+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
int main()
{
	RI i,j,k;for(F.read(n,m),i=1;i<=n;++i) F.read(a[i]);
	for(i=1;i<=m;++i) F.read(x[i],y[i]),++d[x[i]],++d[y[i]];//计算度数
	for(i=1;i<=m;++i) (d[x[i]]^d[y[i]]?d[x[i]]<d[y[i]]:x[i]<y[i])?add(x[i],y[i]):add(y[i],x[i]);//人为定向
	long long ans=0;for(i=1;i<=n;++i)//枚举点
	{
		for(j=lnk[i];j;j=e[j].nxt) f[e[j].to]=i;//打标记
		for(j=lnk[i];j;j=e[j].nxt) for(k=lnk[e[j].to];k;k=e[k].nxt)//枚举再枚举
			f[e[k].to]==i&&(ans+=max(a[i],max(a[e[j].to],a[e[k].to])));//找到三元环计算贡献
	}return printf("%lld",ans),0;
}
posted @ 2020-06-01 19:59  TheLostWeak  阅读(126)  评论(0编辑  收藏  举报