题目描述:
题目链接:bzoj 4264 小c找朋友 PS:这是一道权限题。
题目大意:
给定一张无向图,求满足以下条件的点对(x,y)数目:对任意点 z (z!=x,y),边(x,z)和(y,z)同时存在或同时不存在。
输入格式:
一行两个整数 n,m,分别表示这幅图点数和边的数量。
接下来m行,每行两个整数,表示这两个点之间存在一条无向边。保证没有重边和自环。
输出格式:
一行一个整数,表示答案。
样例输入:
3 3
1 2
2 3
1 3
样例输出:
3
数据范围:
对于前30%的数据,
n,m≤500
对于100%的数据,
n,m≤106
题目分析:
一个点对满足上面的要求,则这两个点所连向的点的集合是一样的(除开它们之间互相连)。于是给每个点赋值(随机赋,但有技巧,见代码),分两种情况讨论:
1、xy之间没有边,直接对每个点所连向的点的集合求和(或者异或和),如果两个点的和相同,就说明它们所连向的点的集合相同。于是它们是满足要求的,记和为同一个的个数为n,ans=n*(n-1)/2.
2、xy之间有边。枚举每条边,如果此边所连的两个点的和分别减去对面的点的权值后相同,说明它们满足要求,ans++。
附代码:
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
using namespace std;
const int N=1e6+100;
const int mod=1e3;
int n,m,tot,nxt[N*2],first[N],to[N*2],a[N],from1,from2,dian1,dian2;
long long w[N],sum[N],b[N],ans,num[N];
int readint()
{
char ch;int i=0,f=1;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') {ch=getchar();f=-1;}
for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
return i*f;
}
void create(int x,int y)
{
tot++;
nxt[tot]=first[x];
first[x]=tot;
to[tot]=y;
}
void discinit()//离散化
{
sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+m+1,sum[i])-b;
}
int main()
{
//freopen("graph.in","r",stdin);
//freopen("graph.out","w",stdout);
int x,y;
n=readint();m=readint();
tot=1;
for(int i=1;i<=m;i++)
{
x=readint();y=readint();
create(x,y);
create(y,x);
}
srand(time(0));
for(int i=1;i<=n;i++)
{
x=(rand()%mod)*(rand()%mod)*(rand()%mod)*(rand()%mod)+1;//连续乘4次,使得后面点集不同但和相同的情况的概率非常非常小
w[i]=x;
}
for(int i=1;i<=n;i++)
{
for(int e=first[i];e;e=nxt[e])
{
sum[i]+=w[to[e]];
}
b[i]=sum[i];
}
discinit();//离散化,来统计和分别相同的有多少个,也可以用hash求
for(int i=1;i<=n;i++)
{
num[a[i]]++;
}
for(int i=1;i<=n;i++)
if(num[i]>1)
{
ans+=num[i]*(num[i]-1)/2;
}
for(int i=1;i<=m;i++)
{
from1=i*2;from2=i*2+1;
dian1=to[from1];dian2=to[from2];
if(sum[dian1]-w[dian2]==sum[dian2]-w[dian1])
ans++;
}
printf("%I64d",ans);
return 0;
}