给定一个 \(n\) 个点 \(m\) 条边的图,点 \(i\) 有权值 \(a_i\),一个三元环 \((i,j,k)\) 的贡献为 \(\max(a_i,a_j,a_k)\),求所有三元环的贡献之和。
\(\texttt{Data Range:}1\leq n\leq 10^5,1\leq m\leq 3\times 10^5\)
所谓的三元环计数其实就是个优雅的暴力。
考虑将原图的边定向:度数大的连向度数小的,度数一样则编号大的连向编号小的。我们可以发现这个图有一些特殊性质:
由于严格规定了连边的方向,所以这个图是个 DAG。
如果一个点在原图中度数大于 \(\sqrt{m}\),注意到这个点只能向原图不小于它的点连边,而且原图中每个点的度数之和是 \(O(m)\),所以这个点在新图上的出度为 \(O(\sqrt{m})\)。
如果一个点在原图中度数不大于 \(\sqrt{m}\),由于新图中的出度不可能比原图还大,所以这个点在新图上的出度为 \(O(\sqrt{m})\)。
所以建完图之后暴力枚举即可,因为出度为 \(O(\sqrt{m})\) 所以我们可以通过打标记的方法快速查询一个点能不能直接到达另一个点,时间复杂度 \(O(m\sqrt{m})\)。
注意一下数三元环的时候用邻接表存新图由于内存访问不连续所以没 vector 存图跑得快,在这题的直接后果就是被卡常。
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=3e5+51;
ll n,m;
li res;
vector<ll>g[MAXN];
ll vis[MAXN],deg[MAXN],from[MAXN],to[MAXN],w[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!=‘-‘)
{
ch=getchar();
}
if(ch==‘-‘)
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-‘0‘);
ch=getchar();
}
return num*neg;
}
int main()
{
n=read(),m=read();
for(register int i=1;i<=n;i++)
{
w[i]=read();
}
for(register int i=1;i<=m;i++)
{
deg[from[i]=read()]++,deg[to[i]=read()]++;
}
for(register int i=1;i<=m;i++)
{
if(deg[from[i]]>deg[to[i]]||(deg[from[i]]==deg[to[i]]&&from[i]>to[i]))
{
swap(from[i],to[i]);
}
g[from[i]].emplace_back(to[i]);
}
for(register int i=1;i<=m;i++)
{
for(register int j:g[i])
{
vis[j]=1;
}
for(register int j:g[i])
{
for(register int k:g[j])
{
res+=vis[k]*max(w[i],max(w[j],w[k]));
}
}
for(register int j:g[i])
{
vis[j]=0;
}
}
printf("%lld\n",res);
}
原文:https://www.cnblogs.com/Karry5307/p/13618318.html