您的位置:首页 > 其它

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

2017-03-31 09:46 393 查看

Description

掌管着世界的暗流的是一个叫做Samjia的人。

他看到所有人的生死,他看见所有人一世又一世的轮回,而他却从未把握过自己的命。

在无法估计的命中,他看见那些轮回,他很好奇,这一切的一切,都是如何开始如何结束,他想,就算是他也会堕入这样的轮回中的吧。

于是他开始数轮回,他看到的是一个有n个点m条边的无向图(边是带标号的),一个轮回是一个由四条边组成的环,环中不能有重复的边,除了起点和终点外(当然,由于是一个环,起点和终点是一样的)不能经过一个同样的点多次。两个轮回被视为不同的当且仅当两个轮回各自的4条边中有一条的编号不同,Samjia想知道,这个system中有多少不同的轮回。

他忙着思考人生,所以数轮回的任务就交给你了。

Solution

比赛的时候直接打了个很稳定的暴力,但是因为数据水,那些打不稳定的暴力都过了。

这题有一个很高级的思路:如果枚举与一个点相邻且度数大于等于它的点,复杂度数O(m−−√)的

证明:如果设点i,与它相邻且度数比它大的点有x个,那么假设这些点每个点与它相邻的边都有x个,所以x∗x≤2∗m=>x≤2∗m−−−−−√,那么枚举的复杂度就是O(x)≤O(2∗m−−−−−√)。

那么这道题就枚举一个点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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: