【NOI2017模拟3.30】轮回(根号算法,暴力)

20 篇文章 0 订阅

Description

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

Solution

比赛的时候直接打了个很稳定的暴力,但是因为数据水,那些打不稳定的暴力都过了。
这题有一个很高级的思路:如果枚举与一个点相邻且度数大于等于它的点,复杂度数 O(m)
证明:如果设点i,与它相邻且度数比它大的点有x个,那么假设这些点每个点与它相邻的边都有x个,所以 xx2m=>x2m ,那么枚举的复杂度就是 O(x)O(2m)
那么这道题就枚举一个点i,然后枚举度数比它大的点j,然后在枚举j连出去的点且都数比它大的点k,然后得出到k的方案数num,对答案贡献(num-1)*num/2。
i和j的复杂度是 O(nm) ,k的总复杂度是2*m的。
但是还有一个小细节,就是如果对于度数相等的情况,如果直接求可能会算重,所以就要搞出优先级,那么直接把度数排一个序就好了。

Code

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define rep(i,a) for(i=first[a];i;i=next[i])
const int maxn=1e5+7;
typedef long long ll;
using namespace std;
int first[maxn*2],last[maxn*2],next[maxn*2],a[maxn],num;
int i,j,k,l,n,m,du[maxn*2],c[maxn],az[maxn],tot,you[maxn],b[maxn];
ll ans,t;
bool bz[maxn];
void add(int x,int y){
    last[++num]=y,next[num]=first[x],first[x]=num;
}
int main(){
    freopen("palingenesis.in","r",stdin);
    freopen("palingenesis.out","w",stdout);
//  freopen("fan.in","r",stdin);
//  freopen("fan.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m){
        scanf("%d%d",&k,&l);du[k]++,du[l]++;
        add(k,l),add(l,k);
    }
    fo(i,1,n)b[du[i]]++;fo(i,1,n)b[i]+=b[i-1];
    fod(i,n,1)you[i]=b[du[i]]--;
    fo(i,1,n){
        tot=0;
        rep(j,i){
            if(you[last[j]]>you[i]){
                rep(k,last[j]){
                    if(you[last[k]]<you[i])continue;
                    if(last[k]==i)continue;
                    if(!bz[last[k]]){
                        az[last[k]]=++tot;
                        bz[last[k]]=1;
                    }
                    a[az[last[k]]]++;
                }
            }
        }
        fo(j,1,tot)ans+=a[j]*(a[j]-1)/2,a[j]=0;
        rep(j,i){
            if(you[last[j]]>=you[i]){
                rep(k,last[j]){
                    if(last[k]==i)continue;
                    bz[last[k]]=0;
                    az[last[k]]=0;
                }
            }
        }
    }
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值