2000个点的有向图,保证任意两点间仅有一条有向边,问是否存在一个三元环。
http://acm.hdu.edu.cn/showproblem.php?pid=4324
解法一:
如果把任意有公共顶点的两边构成的图形看做一个角,可将角分为是b的和不是b的(用a类表示),把任意三条边看做一个三角形,可将三角形分为x、y两种。对于每个顶点入度为in出度为out则共有in*out种b种角,由于是竞赛图,因此有C(n,2)-in*out种a种角,根据3x+y=b,2y=a,因此x=(b-a/2)/3,即可求出三元环的个数。
解法二:
归结为1个条件,任何两人都有边,要么出要么入。
对于有向三元环,我们知道找到:A->B->C->A ,B->C->A->B ,C->A->B->C 是一样的,这给0(n^2)的算法提供了基础。
对于每次枚举i,我们在(0~i-1)的范围看下有多少个i指向的点(剩下的就是指向i的点),同时算下i指向的点的出度和。就可以知道这些 i指向的点 指向 指向i的点(剩下的点)的数目,如果
num * (num - 1) / 2 < sumout
那么就是被指向的点又指回去了,这样就形成了3元环。
否则,剩下的就是更新下出度即可,继续执行下一个节点。
解法三:
结论:对于一个竞赛图,若存在n元环,一定存在一个n-1元环或者三元环,此题可以一遍拓扑排序判环求解即只需要找到一个环,就必定存在三元环。证明如下: 假设存在一个n元环,因为a->b有边,b->a必定没边,反之也成立 所以假设有环上三个相邻的点a-> b-> c,那么如果c->a间有边,就已经形成了一个三元环,如果c->a没边,那么a->c肯定有边,这样就形成了一个n-1元环。。。。 所以只需证明n为4时一定有三元环即可,显然成立。
解法四:
增量算法,充分利用“任意两点间仅有一条有向边”的性质。
假设前面已经加入了N个点,现在来了第N+1个点。
那么一定能将N个点分成left和right两部分,使得N+1号点到left有边,right到N+1号点右边(因为任意两点间都有边),那么,如果left的任意一个点l到right任意一个点r有边的话,那么就有答案N+1->l->r->N+1这样一个长度为3的环。
那么每次加入N+1号点后,用O(N)的复杂度求出左边的数量leftnum,右边的数量rightnum,left的出度和leftout,left的入度和leftin。
如果left没有一条到right的边,则一定满足:
leftin = leftout + leftnum * rightnum(left和right任意两点右边,如果没有左到右的,那么leftnum*rightnum条边都是右到左的)
那么,如果leftin != leftout + leftnum * rightnum,则暴力枚举左点,右点即可得到答案。
总体复杂度O(n^2)
解法五:
直接深搜。
1:
int main(){
scanf("%d",&T);
for(int ca = 1; ca <= T; ca ++){
scanf("%d",&n);
memset(in, 0 , sizeof(in));
memset(out, 0, sizeof(out));
for(int i = 1; i <= n; i ++){
scanf("%s",tmp + 1);
for(int j = 1; j <= n; j ++){
if(tmp[j] == '1'){
in[j] ++;
out[i] ++;
}
}
}
long long b, a, ans, all ;
b = all = 0;
for(int i = 1; i <= n; i ++){
b += in[i] * out[i];
all += (n - 1) * (n - 2) / 2;
}
// all += ( (n - 1) / 2 * (n - 2) )* n; 这么写就错了,加在里面就对,无语了。
// printf("all af = %d\n", all);
a = all - b;
ans = ( b - a / 2 ) / 3;
// printf("%dkaka\n", ans);
printf("Case #%d: ",ca);
if(ans) printf("Yes\n");
else puts("No");
}
return 0;
}
2:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn = 2012;
char s[maxn][maxn];
int InDegree[maxn];
int main(){
int T;
scanf("%d", &T);
for(int t=1; t<=T; t++){
int n;
scanf("%d ", &n);
for(int i=0; i<n; i++)
gets(s[i]);
for(int i=0; i<n; i++){
InDegree[i] = 0;
for(int j=0; j<n; j++){
if(s[i][j] == '0' && i!=j)
InDegree[i]++;
}
}
printf("Case #%d: ", t);
for(int i=0; i<n; i++){
int sum = 0, x = 0;
for(int j=0; j<n; j++)
if(s[j][i] == '1'){
sum += InDegree[j];//分两组,记录喜欢自己那组的入度和
x++;
}
//x为指向i的有几个, sum为指向i的那几个的入度和
if(sum > x*(x-1)/2){
printf("Yes\n");
goto RL;
}
}
printf("No\n");
RL:continue;
}
return 0;
}
4:
#include <cstdio>
#include <cstring>
#define maxn 2010
using namespace std;
int T,n,edgenum, head[maxn * maxn];
char tmp[maxn];
bool vis[maxn], chu[maxn];
inline void initEdge(){
memset(head, -1, sizeof(head));
edgenum = 0;
}
struct Edge{
int v, nxt;
}edge[maxn * maxn];
void addEdge(int u, int v){
edge[edgenum].v = v;
edge[edgenum].nxt = head[u];
head[u] = edgenum ++;
}
bool record[maxn][maxn];
bool dfs(int u, int depth){
if(vis[u]){
if(depth > 2 && record[depth - 3][u])
return true;
return false;
}else{
vis[u] = true;
record[depth][u] = true;
for(int i = head[u]; i != -1; i = edge[i].nxt){
if(dfs(edge[i].v, depth + 1))
return true;
}
}
return false;
}
int main(){
scanf("%d",&T);
for(int ca = 1; ca <= T; ca ++){
scanf("%d",&n);
initEdge();
memset(chu, false, sizeof(chu));
for(int i = 1; i <= n; i ++){
scanf("%s",tmp + 1);
for(int j = 1; j <= n; j ++){
if(tmp[j] == '1'){
addEdge(i,j);
chu[i] = true;
}
}
}
memset(vis, false, sizeof(vis));
memset(record, false, sizeof(record));
bool flag = false;
for(int i = 1; i <= n && !flag; i ++){
if(!vis[i]&&chu[i]){
flag = dfs(i, 0);
}
}
printf("Case #%d: ",ca);
if(flag) printf("Yes\n");
else puts("No");
}
return 0;
}