题意:在一张无向图中,问有多少个点不属于任意一个奇圈,孤立点不属于奇圈
首先明确两个定理:
定理1:对于一个点双连通分量,如果找个一个奇圈那么这个分量的其他点也必然在某个奇圈内。
证明很简单,设ab是一个奇圈上的点,c不属于这个奇圈,设c到a和边数为m到b的边数为n,若m+n为奇数,则c位于由这个奇环除ab外的其他边(偶数条)构成一个奇环,如果m+n为偶数,那么c到ab的路径与ab边构成一个奇环。次定理仅适用于点双连通分量,不适用于边连通分量。
1 4
| \ / \
| \ / \
| 2 5 此图在很多时候被用到,尤其是不太确定应该使用边连通还是点连通时。
| / \ /
| / \ /
3 6
依次此题变为判断一个点连通分量里是否存在一个奇圈,有事又用到了定理2
定理2:一个图是二部图当且仅当它不包含奇圈。这是判定二部图的一个方法,并一个方法时染色,因此我们可以染色的方法盘奇圈,如果一个连通分量中某边的两点染色相同则不是二部图,即存在奇圈,则此连通分量中所有点都不会被去除。
另外一种判断二分图的方法是使用并查集,两点有边说明两点不属于一类,每次加边时用差统计量判断是否矛盾即可,效率应该和染色差不多,但是更好写,sgu172 eXam二分图判定
PS:此题很经典也很综合,目前的瓶颈在于想不到那俩定理。。。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.StreamTokenizer; import java.util.Arrays; public class Main{ int maxn = 1010,maxm=2000010; class node{ int be,ne; node(int b,int e){ be=b; ne=e; } } class block{ node buf[]=new node[maxn]; int E[]=new int[maxn],len; int cut; void init(int c) { cut=c; Arrays.fill(E,-1); len=0; } void add(int a,int b) { buf[len]=new node(b,E[a]); E[a]=len++; } int color[]=new int[maxn]; void work(){ Arrays.fill(color,-1); if(tocolor(cut,0)) return; flag[cut]=1; for(int i=0;i<len;i++) flag[buf[i].be]=1; } boolean tocolor(int a,int c){ boolean res=true; color[a] = c; for (int i=E[a];i!=-1;i=buf[i].ne) { int b = buf[i].be; if (color[b] == -1) res&=tocolor(b, c ^ 1); if (color[b] == c) return false; } return res; } } node buf[]=new node[maxm]; int E[]=new int[maxn],len; void add(int a,int b){ buf[len]=new node(b,E[a]); E[a]=len++; buf[len]=new node(a,E[b]); E[b]=len++; } void init(){ Arrays.fill(E,-1); len=0; } int map[][] = new int[maxn][maxn], n; int dfn[] = new int[maxn], low[] = new int[maxn], cnt; int stack[] = new int[maxn], top; int flag[] = new int[maxn]; block bk=new block(); void dfs(int a) { dfn[a] = low[a] = ++cnt; for (int i=E[a];i!=-1;i=buf[i].ne) { int b=buf[i].be; if (dfn[b] == 0) { stack[++top] = i; dfs(b); if (low[b] < low[a]) low[a] = low[b]; if (low[b] >= dfn[a]) { int x; bk.init(a); do { x = stack[top--]; bk.add(buf[x^1].be, buf[x].be); } while (buf[x^1].be!=a||buf[x].be!= b);// a组成多个块,不能弹出 bk.work(); } } else if (dfn[b] < low[a]){ low[a] = dfn[b]; stack[++top] = i; } } } int solve() { for (int i = 1; i <= n; i++) dfn[i] = low[i] = flag[i] = 0; top = cnt = 0; for(int i=1;i<=n;i++) if(dfn[i]==0) dfs(i); int ans = 0; for (int i = 1; i <= n; i++) if (flag[i] == 0) ans++; return ans; } StreamTokenizer in = new StreamTokenizer(new BufferedReader( new InputStreamReader(System.in))); int nextInt() throws IOException { in.nextToken(); return (int) in.nval; } void run() throws IOException { while(true){ n = nextInt(); int m = nextInt(); if(n==0) break; for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) map[i][j] = map[j][i] = 1; int a, b; while (m-- > 0) { a = nextInt(); b = nextInt(); map[a][b] = map[b][a] = 0; } init(); for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) if(map[i][j]==1) add(i,j); System.out.println(solve()); } } public static void main(String[] args) throws IOException { new Main().run(); } }