題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=3551
題意:有一個n個點,m條邊的圖 ,給出每個點的度數,問是否可以成為該圖的子圖。
不看大佬的博客是真的想不出來。。。
思路:主要是建圖,建完只要跑下一般圖最大匹配就可以。但是這個圖真難啊!!!
將每個度數拆成一個個點,重新編號,拆完的點連向對應的x,y點。
例如例題1:
4 4
1 2
3 4
2 3
1 4
1
2
1
0
按照 度數將 1的1個度編號為1, 2的2個度編號為2和3, 3的一個度編號為4
在根據原圖連接拆點(當然邊也要拆點)。
由 第一條邊1---2: 將1點變成 5, 2變成 6 將每個度的拆點與其對應邊的拆點連接
就是現在1的度拆點1和邊拆5相連 2的度拆點 2和3 與邊拆點 6相連
連接方式: 1--5 2--6 3--6 當然 5---6
第二條邊 3---4 將3點變成 7, 4變成8
連邊方式:4---7 7---8
第三條邊 2---3 將2點變成 9, 3變成10
連邊方式:2---9 3---9 4---10 9---10
第四條邊 1---4 將1點變成11, 4變成12
連邊方式:1---11 11---1
圖如下:

最大匹配: 1---5 2---6 3---9 4---10 7---8 11---12 達到完美匹配所以是可以成為子圖的
再求最大匹配,如果是完美匹配就是子圖。
為什么?
如果達到完美匹配,這條邊的另一頭必定匹配着另一個點的一個度,表示拆點原點相連,這樣一條邊的匹配是合乎要求的。
例如上面 1--5 2--6 就是1的一個度連接2的一個度 3--9 4--10就是2的一個度連接3的一個度
而7---8 11---12表示 應該刪除原先邊3---4 1---4. 就是子圖的樣子即子圖 1---2 2---3
即最大匹配中度的拆點與邊的拆點匹配時就是子圖中連接邊的一個點 而通過邊拆點的連接連向另一個點, 而只有邊的拆點與邊的拆點相連 就是刪除的邊
代碼:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=1050; bool g[maxn][maxn],inque[maxn],inpath[maxn]; bool inhua[maxn]; int st,ed,newbase,ans,n; int base[maxn],pre[maxn],match[maxn]; int head,tail,que[maxn]; int x[maxn],y[maxn],f[maxn],mp[maxn][maxn],ne,np; void Push(int u) { que[tail]=u; tail++; inque[u]=1; } int Pop() { int res=que[head]; head++; return res; } int lca(int u,int v)//尋找公共花祖先 { memset(inpath,0,sizeof(inpath)); while(1) { u=base[u]; inpath[u]=1; if(u==st) break; u=pre[match[u]]; } while(1) { v=base[v]; if(inpath[v]) break; v=pre[match[v]]; } return v; } void reset(int u)//縮環 { int v; while(base[u]!=newbase) { v=match[u]; inhua[base[u]]=inhua[base[v]]=1; u=pre[v]; if(base[u]!=newbase) pre[u]=v; } } void contract(int u,int v)// { newbase=lca(u,v); memset(inhua,0,sizeof(inhua)); reset(u); reset(v); if(base[u]!=newbase) pre[u]=v; if(base[v]!=newbase) pre[v]=u; for(int i=1;i<=n;i++) { if(inhua[base[i]]){ base[i]=newbase; if(!inque[i]) Push(i); } } } void findaug() { memset(inque,0,sizeof(inque)); memset(pre,0,sizeof(pre)); for(int i=1;i<=n;i++)//並查集 base[i]=i; head=tail=1; Push(st); ed=0; while(head<tail) { int u=Pop(); for(int v=1;v<=n;v++) { if(g[u][v]&&(base[u]!=base[v])&&match[u]!=v) { if(v==st||(match[v]>0)&&pre[match[v]]>0)//成環 contract(u,v); else if(pre[v]==0) { pre[v]=u; if(match[v]>0) Push(match[v]); else//找到增廣路 { ed=v; return ; } } } } } } void aug() { int u,v,w; u=ed; while(u>0) { v=pre[u]; w=match[v]; match[v]=u; match[u]=v; u=w; } } void edmonds()//匹配 { memset(match,0,sizeof(match)); for(int u=1;u<=n;u++) { if(match[u]==0) { st=u; findaug();//以st開始尋找增廣路 if(ed>0) aug();//找到增廣路 重新染色,反向 } } } //以上是帶花樹求最大匹配算法 不用看 void create()//建圖 { n=0; memset(g,0,sizeof(g)); for(int i=1;i<=np;i++) for(int j=1;j<=f[i];j++) mp[i][j]=++n;//拆點,給每個度的點編號 for(int i=0;i<ne;i++) {//此時n+1代表x,n+2代表y for(int j=1;j<=f[x[i]];j++) g[mp[x[i]][j]][n+1]=g[n+1][mp[x[i]][j]]=1;//每個度的點與對應的x,y相連 for(int j=1;j<=f[y[i]];j++) g[mp[y[i]][j]][n+2]=g[n+2][mp[y[i]][j]]=1; g[n+1][n+2]=g[n+2][n+1]=1;//x與y相連 n+=2; } } void print() { ans=0; for(int i=1;i<=n;i++) if(match[i]!=0) { ans++; // if(match[i]>i) // cout<<"_____"<<i<<' '<<match[i]<<endl; } //cout<<"******"<<ans<<' '<<n<<endl; if(ans==n) printf("YES\n"); else printf("NO\n"); } int main() { int t,k=0; scanf("%d",&t); while(t--) { scanf("%d%d",&np,&ne); for(int i=0;i<ne;i++) scanf("%d%d",&x[i],&y[i]); for(int i=1;i<=np;i++) scanf("%d",&f[i]); printf("Case %d: ",++k); create(); edmonds(); print(); } return 0; }