轮回

题目描述

掌管着世界的暗流的是一个叫做Samjia的人。
他看到所有人的生死,他看见所有人一世又一世的轮回,而他却从未把握过自己的命。
在无法估计的命中,他看见那些轮回,他很好奇,这一切的一切,都是如何开始如何结束,他想,就算是他也会堕入这样的轮回中的吧。
于是他开始数轮回,他看到的是一个有n个点m条边的无向图(边是带标号的),一个轮回是一个由四条边组成的环,环中不能有重复的边,除了起点和终点外(当然,由于是一个环,起点和终点是一样的)不能经过一个同样的点多次。两个轮回被视为不同的当且仅当两个轮回各自的4条边中有一条的编号不同,Samjia想知道,这个system中有多少不同的轮回。
他忙着思考人生,所以数轮回的任务就交给你了。

题解

好强啊!
我们来定义一下优先级吧!
两个点,度数大的优先级高。
度数一样则编号大的优先级高。
对于一个四元环,我们设k是其中优先级最大的,i与k处于同一对角线。
我们可以枚举i,对于一次i的枚举,需要一个cnt数组,初始全0,cnt[k]表示目前有多少i->j->k,其中k当然要是这里面优先级最大的。
我们接着来枚举j,(i,j)的枚举是O(m)的。
接着我们枚举k,判断k是否是优先级最大的,然后答案加上cnt[k],接着再把cnt[k]加一。
容易看出这样是正确且不会计重的,那么枚举k的复杂度是多少呢?
一个点,与它相连且度数不比它小的点的个数是 m
假设一个点有x个这样的点,那么这些点度数至少为x,这样就有x^2条边。
那么可证明这个结论。
所以这个算法复杂度为 O(mm)

#include<cstdio>
#include<algorithm>
#include<map>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
int edge[maxn][2],h[maxn],go[maxn*2],next[maxn*2],d[maxn],cnt[maxn];
int h2[maxn],g2[maxn*2],n2[maxn*2];
int i,j,k,l,r,t,x,y,z,w,n,m,ans,tot,top;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y){
    d[y]++;
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void add2(int x,int y){
    g2[++top]=y;
    n2[top]=h2[x];
    h2[x]=top;
}
int main(){
    freopen("palingenesis.in","r",stdin);freopen("palingenesis.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m){
        edge[i][0]=j=read();edge[i][1]=k=read();
        add(j,k);add(k,j);
    }
    fo(i,1,m){
        j=edge[i][0];k=edge[i][1];
        if (d[j]<d[k]||d[j]==d[k]&&j<k) add2(j,k);
        if (d[k]<d[j]||d[k]==d[j]&&k<j) add2(k,j);
    }
    fo(i,1,n){
        t=h[i];
        while (t){
            j=go[t];
            r=h2[j];
            while (r){
                k=g2[r];
                if (d[i]<d[k]||d[i]==d[k]&&i<k){
                    ans+=cnt[k];
                    cnt[k]++;
                }
                r=n2[r];
            }
            t=next[t];
        }
        t=h[i];
        while (t){
            j=go[t];
            r=h2[j];
            while (r){
                k=g2[r];
                if (d[i]<d[k]||d[i]==d[k]&&i<k) cnt[k]=0;
                r=n2[r];
            }
            t=next[t];
        }
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值