[HackerRank Week of Code]Jogging Cats/[JZOJ5037]轮回

28 篇文章 0 订阅
9 篇文章 0 订阅

题目大意

给定一个 n 个点,m条边的无重边和自环的无向图,求图中不同的四元环个数。

1n5×104,1m105


题目分析

首先我们要知道一个性质:任何一个点,与其直接相连的度数大于等于它的点最多只有 2m 个。
证明:设有 x 个这样的点,这个点度数至少为x,那这 x 个这样的点每个点度数都大于等于x,但是总边数是 2m (正反两向),因此我们有 x22m ,两边开根号得到 x2m
考虑将所有点按照度数先排一个序,令点 x 的排名为rankx,我们现在来考虑怎么求答案。
四元环
考虑这样一个四元环,我们假设B B 是四个点中rank最大的,计算这样的 B 的个数就可以不重不漏地计算出四元环的个数。
考虑枚举点A,然后枚举与其相邻的点 y ,然后再枚举所有rank y 大的与y相邻的点,这些点显然都可能作为 B 点,我们维护一个计数器来计算之前B被枚举多少次,答案加上计数器的值,然后计数器加一。
枚举完 A 之后,我们用和枚举时一样的方法来清空计数器就好了。
枚举yy的复杂度之和是 m 的,枚举与y相邻的 B 的复杂度每次是m的。于是总的复杂度是 O(mm) 的。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=50005;
const int M=100005;
const int E=M<<1;
const int B=500;

int deg[N],kth[N],rank[N],cnt[N];
int tov[E],nxt[E];
int key[N][B];
int last[N];
int n,m,tot;
LL ans;

bool cmp(int x,int y){return deg[x]<deg[y];}

void insert(int x,int y){tov[++tot]=y,nxt[tot]=last[x],last[x]=tot;}

void pre()
{
    for (int i=1;i<=n;++i) kth[i]=i;
    sort(kth+1,kth+1+n,cmp);
    for (int i=1;i<=n;++i) rank[kth[i]]=i;
    for (int x=1;x<=n;++x)
        for (int i=last[x],y;i;i=nxt[i])
            if (rank[y=tov[i]]>rank[x]) key[x][++key[x][0]]=y;
}

void calc()
{
    for (int x=1;x<=n;++x)
    {
        for (int i=last[x],y;i;i=nxt[i])
        {
            y=tov[i];
            for (int j=1,z;j<=key[y][0];++j)
                if (rank[z=key[y][j]]>rank[x]) ans+=(cnt[z]++);
        }
        for (int i=last[x],y;i;i=nxt[i])
        {
            y=tov[i];
            for (int j=1,z;j<=key[y][0];++j)
                if (rank[z=key[y][j]]>rank[x]) --cnt[z];
        }
    }
}

int main()
{
    freopen("palingenesis.in","r",stdin),freopen("palingenesis.out","w",stdout);
    n=read(),m=read();
    for (int i=1,x,y;i<=m;++i) x=read(),y=read(),insert(x,y),insert(y,x),++deg[x],++deg[y];
    pre(),calc();
    printf("%lld\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值