Lightoj 1380 1384 最小树形图
两道最小树形图。所谓最小树形图指的是有向图的最小生成树。
参考了多位大神的博文终于自己写出来了。。
有向图的最小生成树指的是在给定的有向图上找到一个子图
点集与原图相同。边集是原边集的子集。
有定根的生成树指的是规定某个点为生成树的根,向外延边扩展形成生成树。
算法名字叫做 刘朱算法。复杂度是O(VE)
参考博客 http://www.cppblog.com/RyanWang/archive/2010/01/25/106427.aspx
1380是很明显的最小树形图题目,直接对给定有向图求树形图即可。
模版题
实现起来还是有很多要注意的细节。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <vector> #include <algorithm> #include <queue> #include <cstdio> #include <stack> #include <cstring> using namespace std; struct edge{ int p,l; edge(int p,int l):p(p),l(l){} }; const int MAXN = 1010; const int INF = 0x64646464; vector <edge> e[MAXN<<1]; typedef vector<edge>::iterator iter; int T,N,M,K,cas=0; int p1,p2,l; int prev[MAXN<<1]; int minc[MAXN<<1]; bool del[MAXN<<1]; int vis[MAXN<<1]; bool wor[MAXN<<1]; int value[MAXN<<1]; int ne[MAXN<<1]; int all; stack <int> s; int dfs(int p){ int rul=1; vis[p]=true; for (iter k=e[p].begin();k!=e[p].end();k++) if (!vis[k->p]) rul+=dfs(k->p); return rul; } bool dfs2(int p,int index){ if (p==K) return false; if (vis[p]==index) return true; vis[p]=index; s.push(p); if (dfs2(prev[p],index)) return true; s.pop(); return false; } void merge(){ memset(ne,100,sizeof(ne)); memset(wor,0,sizeof(wor)); int P = prev[s.top()]; int now = -1; while (now!=P){ now = s.top(); wor[now]=true; s.pop(); for (iter k=e[now].begin();k!=e[now].end();k++) ne[k->p]=min(ne[k->p],k->l); } //out 向外的边 for (int i=0;i<all;i++){ if (del[i]) continue; if (wor[i]) continue; if (ne[i]==INF) continue; e[all].push_back(edge(i,ne[i])); } //in 向内的边 for (int i=0;i<all;i++){ int l = INF; if (del[i]) continue; if (wor[i]) continue; for (iter k=e[i].begin();k!=e[i].end();k++){ if (!wor[k->p]) continue; l=min(l,k->l-minc[k->p]); } if (l==INF) continue; e[i].push_back(edge(all,l)); } for (int i=0;i<all;i++) if (wor[i]){ del[i]=true; value[all]+=minc[i]; value[all]+=value[i]; //缩点时要加上原来的点的权 } all++; } int main(){ freopen("in.txt","r",stdin); scanf("%d",&T); while (T--){ memset(del,0,sizeof(del)); memset(value,0,sizeof(value)); for (int i=0;i<(MAXN<<1);i++) e[i].clear(); memset(vis,0,sizeof(vis)); scanf("%d%d%d",&N,&M,&K); for (int i=1;i<=M;i++){ scanf("%d%d%d",&p1,&p2,&l); e[p1].push_back(edge(p2,l)); } if (dfs(K)!=N){ printf("Case %d: impossible\n",++cas); continue; } all=N; bool flag=false; while(!flag){ memset(minc,100,sizeof(minc)); memset(vis,-1,sizeof(vis)); for (int i=0;i<all;i++){ if (del[i]) continue; for (iter k=e[i].begin();k!=e[i].end();k++){ if (k->l<minc[k->p]){ minc[k->p]=k->l; prev[k->p]=i; } } } flag=true; for (int i=0;i<all;i++){ if (del[i]) continue; if (vis[i]!=-1) continue; if (dfs2(i,i)) { flag=false; merge(); break; } } } int ans=0; for (int i=0;i<all;i++){ if (i==K) continue; if (del[i]) continue; ans+=minc[i]; ans+=value[i]; } printf("Case %d: %d\n",++cas,ans); } }
1384则多了一个限制条件。。在规定的费用内使得带宽最大。
二分最大的带宽,然后在不考虑小于当前带宽的边的情况下计算最小树形图的权值
(如果不存在则返回最大)
权值小于规定的费用则当前带宽合法,否则不合法。。
复杂度为 (VE*log(m))
直接修改1380的代码,加上二分即可。
注意初始化问题,在上次计算后可能有新的边在邻接表中,
将编号大于等于N的边全部删除即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <vector> #include <algorithm> #include <queue> #include <cstdio> #include <stack> #include <cstring> using namespace std; struct edge{ int p,l,c; edge(int p,int l,int c):p(p),l(l),c(c){} }; const int MAXN = 60; const int INF = 0x64646464; vector <edge> e[MAXN<<1]; typedef vector<edge>::iterator iter; int T,N,M,K,C,cas=0; int p1,p2,l,c; int mid; int prev[MAXN<<1]; int minc[MAXN<<1]; bool del[MAXN<<1]; int vis[MAXN<<1]; bool wor[MAXN<<1]; int value[MAXN<<1]; int ne[MAXN<<1]; int cc[10010]; int all; stack <int> s; int dfs(int p){ int rul=1; vis[p]=true; for (iter k=e[p].begin();k!=e[p].end();k++) if (k->c>=cc[mid] && !vis[k->p]) rul+=dfs(k->p); return rul; } bool dfs2(int p,int index){ if (p==K) return false; if (vis[p]==index) return true; vis[p]=index; s.push(p); if (dfs2(prev[p],index)) return true; s.pop(); return false; } void merge(){ memset(ne,100,sizeof(ne)); memset(wor,0,sizeof(wor)); int P = prev[s.top()]; int now = -1; while (now!=P){ now = s.top(); wor[now]=true; s.pop(); for (iter k=e[now].begin();k!=e[now].end();k++){ if (k->c<cc[mid]) continue; ne[k->p]=min(ne[k->p],k->l); } } //out for (int i=0;i<all;i++){ if (del[i]) continue; if (wor[i]) continue; if (ne[i]==INF) continue; e[all].push_back(edge(i,ne[i],cc[mid])); } //in for (int i=0;i<all;i++){ int l = INF; if (del[i]) continue; if (wor[i]) continue; for (iter k=e[i].begin();k!=e[i].end();k++){ if (!wor[k->p]) continue; if (k->c<cc[mid]) continue; l=min(l,k->l-minc[k->p]); } if (l==INF) continue; e[i].push_back(edge(all,l,cc[mid])); } for (int i=0;i<all;i++) if (wor[i]){ del[i]=true; value[all]+=minc[i]; value[all]+=value[i]; } all++; } int check(){ memset(del,0,sizeof(del)); memset(value,0,sizeof(value)); for (int i=0;i<N;i++) while (!e[i].empty() && e[i].back().p>=N) e[i].pop_back(); for (int i=N;i<=all;i++) e[i].clear(); memset(vis,0,sizeof(vis)); if (dfs(K)!=N) return INF; all=N; bool flag=false; while(!flag){ memset(minc,100,sizeof(minc)); memset(vis,-1,sizeof(vis)); for (int i=0;i<all;i++){ if (del[i]) continue; for (iter k=e[i].begin();k!=e[i].end();k++){ if (k->c<cc[mid]) continue; if (k->l<minc[k->p]){ minc[k->p]=k->l; prev[k->p]=i; } } } flag=true; for (int i=0;i<all;i++){ if (del[i]) continue; if (vis[i]!=-1) continue; if (dfs2(i,i)) { flag=false; merge(); break; } } } int ans=0; for (int i=0;i<all;i++){ if (i==K) continue; if (del[i]) continue; ans+=minc[i]; ans+=value[i]; } return ans; } int main(){ freopen("in.txt","r",stdin); scanf("%d",&T); while (T--){ scanf("%d%d%d",&N,&M,&C); for (int i=0;i<(MAXN<<1);i++) e[i].clear(); for (int i=1;i<=M;i++){ scanf("%d%d%d%d",&p1,&p2,&c,&l); e[p1].push_back(edge(p2,l,c)); cc[i]=c; } sort(cc+1,cc+M+1); int r = unique(cc+1,cc+M+1)-cc-1; int l = 1; int ans=-1; while (l<=r){ mid = (l+r)>>1; int now = check(); if (now<=C) { ans = mid; l=mid+1; } else r=mid-1; } if (ans==-1) printf("Case %d: impossible\n",++cas); else printf("Case %d: %d kbps\n",++cas,cc[ans]); } }