【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;
}
待到再迷茫时回头望,所有脚印会发出光芒