三元环是什么?
三元环是
求无向图的三元环有两种方法:
做法1:
①统计每个点的度数
②入度$sqrt(m)$的分为第二类
③对于第一类,暴力每个点,然后暴力这个点的任意两条边,再判断这两条边的另一个端点是否连接
因为m条边最多每条边遍历一次,然后暴力的点的入度,所以复杂度约为
④对于第二类,直接暴力任意三个点,判断这三个点是否构成环,因为这一类点的个数不会超过$sqrt(m)个,所以复杂度约为$$O(sqrt(m)^3)=O(msqrt(m))$
⑤判断两个点是否连接可以用set,map和Hash都行,根据具体情况而行
这种做法建的是双向边,常数很大
#include
using namespace std;
const int N=1e6+5;
int n,m,x,y,z,ans,du[N],vis[N];
vectora,b,g[N];
mapmp[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
du[x]++;
du[y]++;
mp[x][y]=1;
mp[y][x]=1;
}
for(int i=1;i<=n;i++)
if(du[i]<=sqrt(m))a.push_back(i);
else b.push_back(i);
for(int i=0;i<a.size();i++)
{
x=a[i];
vis[x]=true;
for(int j=0;j<g[x].size();j++)
{
y=g[x][j];
if(vis[j])continue;
for(int k=j+1;k<g[x].size();k++)
{
z=g[x][k];
if(!vis[z]&&mp[y].count(z))ans++;
}
}
}
for(int i=0;i<b.size();i++)
{
for(int j=i+1;j<b.size();j++)
{
x=b[i];y=b[j];
if(mp[x].count(y)==0)continue;
for(int k=j+1;k<b.size();k++)
{
z=b[k];
if(mp[x].count(z)&&mp[y].count(z))ans++;
}
}
}
cout<<ans;
return 0;
}
更优的做法2:
建有向边 复杂度为$O(msqrt(n))$
对所有边按照两端点的度数定向,度数小的往度数大的走,度数相同则按编号(值)小到大走,这样定向后
可以保证是个有向无环图。
为什么呢,要想定向存在环,则这个环上的点度数必须相同,由于保证了编号从小到大走,所以是不可能存在
环的。
这样定向同时还保证了每个点的出度不超过$sqrt(n)$,乍一眼看觉得应该是$sqrt(m)$的,仔细一想确实比这个
低,不过不知道怎么证。
#include
#define P pair
using namespace std;
const int N=5e5+5;
vector >G[N];
int n,m,Sz,u,v,X[N],Y[N],pos[N],vis[N],du[N];
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
Sz=sqrt(m);
for(int i=1;i<=n;i++)
{
vis[i]=du[i]=pos[i]=0;
G[i].clear();
}
for(int i=0;i<m;i++)
{
scanf("%d%d",&X[i],&Y[i]);
du[X[i]]++;
du[Y[i]]++;
}
for(int i=0;i<m;i++)
{
if(du[X[i]]<du[Y[i]])G[X[i]].push_back(make_pair(Y[i],i));
else if(du[Y[i]]<du[X[i]])G[Y[i]].push_back(P(X[i],i));
else{
if(X[i]<Y[i])G[X[i]].push_back(P(Y[i],i));
else G[Y[i]].push_back(P(X[i],i));
}
}
long long ans=0;
for(int i=0;i<m;i++)
{
u=X[i];v=Y[i];
for(int j=0;j<G[u].size();j++)
{
P xu=G[u][j];
int jia=xu.first;
pos[jia]=xu.second;
vis[jia]=i+1;
}
for(int j=0;j<G[v].size();j++)
{
P xu=G[v][j];
int jia=xu.first;
if(vis[jia]==i+1)ans++;
}
}
printf("%lld\n",ans);
}
return 0;
}
题目: Counting Stars
给一张n个点m条边的无向图,问有多少个
其中满足 &&
显然是由两个有公共边的三元环构成的
题解:
我们记录一下每条边能构成的三元环数, 为第i条边的三元环数
我们用做法二做这题:
#include
#define P pair
using namespace std;
const int N=5e5+5;
vector >G[N];
int n,m,Sz,u,v,X[N],Y[N],cnt[N],pos[N],vi[N],du[N];
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
Sz=sqrt(m);
for(int i=1;i<=n;i++)
{
vi[i]=du[i]=pos[i]=0;
G[i].clear();
}
for(int i=0;i<m;i++)
{
scanf("%d%d",&X[i],&Y[i]);
du[X[i]]++;
du[Y[i]]++;
}
for(int i=0;i<m;i++)
{
cnt[i]=0;
if(du[X[i]]<du[Y[i]])G[X[i]].push_back(make_pair(Y[i],i));
else if(du[Y[i]]<du[X[i]])G[Y[i]].push_back(P(X[i],i));
else{
if(X[i]<Y[i])G[X[i]].push_back(P(Y[i],i));
else G[Y[i]].push_back(P(X[i],i));
}
}
long long ans=0;
for(int i=0;i<m;i++)
{
u=X[i];v=Y[i];
for(int j=0;j<G[u].size();j++)
{
P vp=G[u][j];
pos[vp.first]=vp.second;
vi[vp.first]=i+1;
}
for(int j=0;j<G[v].size();j++)
{
P vp=G[v][j];
int vv=vp.first;
if(vi[vv]==i+1)
{
cnt[i]++;
cnt[pos[vv]]++;
cnt[vp.second]++;
}
}
}
for(int i=0;i<m;i++)ans+=1ll*cnt[i]*(cnt[i]-1)/2;
printf("%lld\n",ans);
}
return 0;
}
题目: 3498: PA2009 Cakes
N个点m条边,每个点有一个点权a。
对于任意一个三元环(j,j,k)(i<j<k),它的贡献 为max(ai,aj,ak)
求所有三元环的贡献和。
N<100000,,m<250000。
题解:
按照值从大到小排序,用做法二找到环就把加上点i的值
#include
using namespace std;
const int N=1e6+5;
int n,m,rank[N],du[N],vis[N];
struct node{
int val,id;
}a[N];
vectorg[N];
mapmp[N];
char buf[1<<21],*p1=buf,*p2=buf;
inline int gc()
{
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
int ret=0,f=0;
char c=gc();
while(!isdigit(c))
{
if(c=='-')f=1;
c=gc();
}
while(isdigit(c))
{
ret=ret*10+c-48;
c=gc();
}
if(f)return -ret;
return ret;
}
bool cmp(node a,node b)
{
return a.val>b.val;
}
int main()
{
freopen("xjh.in","r",stdin);
freopen("xjh.out","w",stdout);
n=read();m=read();
int size=sqrt(m);
for(int i=1;i<=n;i++)
{
a[i].val=read();
a[i].id=i;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)rank[a[i].id]=i;
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
if(rank[x]<rank[y])
{
g[x].push_back(y);
mp[x][y]=1;
du[x]++;
}
else{
g[y].push_back(x);
mp[y][x]=1;
du[y]++;
}
}
long long ans=0;
for(int i=1;i<=n;i++)
{
int x=a[i].id;
for(int j=0;j<g[x].size();j++)
vis[g[x][j]]=x;
for(int j=0;j<g[x].size();j++)
{
int y=g[x][j];
if(du[y]<=size)
{
for(int k=0;k<g[y].size();k++)
{
int z=g[y][k];
if(vis[z]==x)ans+=a[i].val;
}
}
else{
for(int k=0;k<g[x].size();k++)
{
int z=g[x][k];
if(mp[y].count(z))ans+=a[i].val;
}
}
}
}
cout<<ans;
}
题目:5407: girls
有一个图,你要选定三个点,保证三个点之间两两无互相连边,当点的时候,你获得的收益为,求所有符合情况的收益和。
题解:
可以用容斥原理,即所有的情况−至少有一对有连边+至少有两对有连边−三对都有连边 。
1. 对于所有的情况,可以考虑每个点来算贡献
2. 对于至少有一对连边的情况,可以枚举一个点和它的连边,然后用前缀和计算处余下来的所有的点。
3. 对于至少有两对连边的情况,可以枚举中间那个于两个点都有连边的点,然后对于它连接的每一个点计算贡献
4. 对于至少有三对连边的情况,可以三元环怒搞
#include
using namespace std;
#define ll long long
const int N=1e6+5;
int n,m;
unsigned long long A,B,C,x,y,z,ans,xu[N],du[N],suma[N],sumb[N],sumc[N];
bool vis[N];
vectora,b,g[N];
mapmp[N];
signed main()
{
scanf("%d%d",&n,&m);
cin>>A>>B>>C;
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;y++;
du[x]++;du[y]++;
g[x].push_back(y);
g[y].push_back(x);
mp[x][y]=1;
mp[y][x]=1;
}
for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
for(int i=1;i<=n;i++)
{
ans+=A*(i-1)*((ll)(n-i-1)*(n-i)/2);
ans+=B*(i-1)*(i-1)*(n-i);
ans+=C*(i-1)*((ll)(i-1)*(i-2)/2);
}
for(int i=1;i<=n;i++)
{
suma[i]=suma[i-1]+(i-1)*A;
sumb[i]=sumb[i-1]+(i-1)*B;
sumc[i]=sumc[i-1]+(i-1)*C;
}
for(int i=1;i<=n;i++)
{
int siz=g[i].size()-1;
unsigned long long cnt0=0,cnt1=0;
for(int j=0;j<=siz;j++)
{
int v=g[i][j];
if(v<i)
{
cnt0++;
ans-=suma[v-1]+B*(v-1)*(v-1)+C*(i-1)*(v-1);
ans+=A*(v-1)*(siz-j)+B*(v-1)*j;
}
else{
cnt1++;
ans-=A*(i-1)*(n-v)+B*(v-1)*(n-v)+sumc[n]-sumc[v];
ans-=A*(i-1)*(v-i-1)+sumb[v-1]-sumb[i]+C*(v-1)*(v-i-1);
ans+=C*(v-1)*j+B*(v-1)*(siz-j);
}
}
ans+=A*(i-1)*(cnt1*(cnt1-1)/2)+B*(i-1)*cnt0*cnt1+C*(i-1)*cnt0*(cnt0-1)/2;
}
int size=sqrt(m);
for(int i=1;i<=n;i++)
if(du[i]<=size)a.push_back(i);
else b.push_back(i);
for(int i=0;i<a.size();i++)
{
x=a[i];
vis[x]=true;
for(int j=0;j<g[x].size();j++)
{
y=g[x][j];
if(vis[j])continue;
for(int k=j+1;k<g[x].size();k++)
{
z=g[x][k];
if(vis[z])continue;
if(mp[y].count(z))
{
xu[1]=x;xu[2]=y;xu[3]=z;
sort(xu+1,xu+3+1);
ans-=A*(xu[1]-1)+B*(xu[2]-1)+C*(xu[3]-1);
}
}
}
}
for(int i=0;i<b.size();i++)
{
for(int j=i+1;j<b.size();j++)
{
x=b[i];y=b[j];
if(mp[x].count(y)==0)continue;
for(int k=j+1;k<b.size();k++)
{
z=b[k];
if(mp[x].count(z)&&mp[y].count(z))
{
xu[1]=x;xu[2]=y;xu[3]=z;
sort(xu+1,xu+3+1);
ans-=A*(xu[1]-1)+B*(xu[2]-1)+C*(xu[3]-1);
}
}
}
}
cout<<ans;
return 0;
}
/*
4 3
2 3 4
0 1
1 2
2 0
*/
有向图的三元环
有向图的三元环和无向图的不一样,一般是用bitset做。
时间复杂度:
题目:CF117C
一个tournament是一个没有自环的有向图,同时,每两个点之间有一条边连接。这就是说,对于两个点u,v(u≠v),有一条从u到v的边或一条从v到u的边。
给你一个tournament,请找出一个长度为3的环。
题解:
都已每个点都用bitset记录他的入点,出点。
#include
using namespace std;
const int N=5005;
int n,x;
bitsetxu,ru[N],chu[N];
inline int read()
{
int res=0;
char c;
c=getchar();
while(!isdigit(c))c=getchar();
res=c-48;
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
x=read();
if(x==1)
{
ru[i][j]=1;
chu[j][i]=1;
}
}
for(int i=n;i;i--)
for(int j=n;j;j--)
if(i!=j&&ru[i][j]==1)
{
xu=chu[i]&ru[j];
if(xu.count()==0)continue;
printf("%d %d %d",i,j,xu._Find_first());
return 0;
}
cout<<-1;
}
全部评论
(1) 回帖