Online Judge | Problem Set | Authors | Online Contests | User | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Web Board Home Page F.A.Qs Statistical Charts | Current Contest Past Contests Scheduled Contests Award Contest |
做死我了……算法的原理挺很简单的,写起来超级复杂…… 调了一天了…… 不行,我要可耻地贴代码!!(代码略长,3380B,不过这是因为注释写得太多了!!) #include <stdio.h> #include <string.h> #define MAXN 510 #define MAXL 20 #define INF (1<<28) int n,limit,tot=0,dist[MAXN][MAXN]; //tot为小矮人的总数 int deg0,ncc,d[MAXN],pre[MAXN]={0}; //deg0为原图中V0的度,pre[i]为生成树上i的前点 int best[MAXN],bestnum[MAXN],rev[MAXN]; //best[i]为i到根节点V0路径上最长边的值,bestnum[i]表示最长边的编号,即最长边是<bestnum[i], pre[bestnum[i]]> int ans=0; //ans为最终的答案 char name[MAXN][MAXL]; bool visit[MAXN],flag[MAXN]; //flag[i]表示i是否已被加入到最小生成森林中 void GetData(){ int i,j,x,u,v; char str[MAXL]; scanf("%d", &n); strcpy(name[0], "Park"); for (i=0;i<MAXN;++i) for (j=0;j<MAXN;++j) dist[i][j]=INF; for (i=0;i<n;++i){ scanf("%s", str); for (u=-1,j=0;j<=tot;++j) if ( strcmp(str, name[j])==0 ) {u=j;break;} if (u<0) strcpy(name[u=++tot], str); scanf("%s", str); for (v=-1,j=0;j<=tot;++j) if ( strcmp(str, name[j])==0 ) {v=j;break;} if (v<0) strcpy(name[v=++tot], str); scanf("%d", &x); if (dist[u][v]>x) dist[u][v]=dist[v][u]=x; } } int Prim(){ //求最小生成树,将生成树中的节点编号保存在node[ncc][]中,并且返回生成树所连接的节点的个数 int s,i,j,k,cnt,mind,res=0; //res为本棵生成树的大小 for (mind=INF,i=1;i<=tot;++i) //找一个未添加到最小生成森林中,且距离源点V0最近的点s,作为本颗子树的根节点进行拓展 if (!flag[i] && dist[0][i]<mind) mind=dist[0][s=i]; for (i=1;i<=tot;++i){ if (flag[i]) continue; //注意只对尚未加入生成森林中的节点进行操作(否则将不慎修改已处理过的点的pre[]指针) visit[i]=false; pre[i]=s; d[i]=dist[s][i]; } pre[s]=d[s]=0; //直接将s的根置为V0,后面的编程会很方便 flag[s]=visit[s]=true; for (cnt=i=1;i<tot;++i){ for (mind=INF,j=1;j<=tot;++j) if (!visit[j] && mind>d[j]) mind=d[k=j]; if (mind==INF) break; //图形不连通,结束算法 flag[k]=visit[k]=true; ++cnt; //已加入树中的节点数+1 res+=d[k]; for (j=1;j<=tot;++j) if (!visit[j] && d[j]>dist[k][j]) d[j]=dist[pre[j]=k][j]; } ans += res + dist[0][s]; return cnt; } void Reverse(int i){ //维护: 删除最长边后有些节点到根节点V0的距离就变了,要注意把相应的边反向,即i和pre[i]互换 int top=0,end=bestnum[i]; rev[0]=0; while (i!=end) {rev[++top]=i;i=pre[i];} rev[++top]=i; for (i=top;i>0;--i) pre[rev[i]]=rev[i-1]; } int GetBest(int i){ //记忆化搜索 if (best[i]) return best[i]; //已计算过则返回 if (!pre[i]) return best[i]=-INF; int tmp=GetBest(pre[i]); //tmp=best[pre[i]]; if (tmp<dist[pre[i]][i]){ best[i]=dist[pre[i]][i]; //best[i]为 边<pre[i],i> 和 pre[i]到V0路径上的最长边 中较长的一个 bestnum[i]=i; } else{ best[i]=tmp; bestnum[i]=bestnum[pre[i]]; } return best[i]; } void Solve(){ scanf("%d", &limit); int i,k,ktree,choose,delta; for (deg0=i=1;i<n;++i) if (dist[0][i]!=INF) ++deg0; memset(flag,0,sizeof(flag)); ans=ncc=k=0; while (k<tot) {++ncc;k+=Prim();} //先求除去点V0外的最小生成森林。k表示已选择的小矮人的个数,ncc为除去V0点后连通分量的总数 ktree=ans; //ktree为此时度限制生成树的大小 while (ncc<limit){ //ncc为此时V0的度 if (ncc==deg0) break; //此时已无法更新,因为V0连出的所有边都被选用了,可直接退出循环 ++ncc; memset(best,0,sizeof(best)); memset(bestnum,0,sizeof(bestnum)); best[0]=-INF; //预处理任一点到V0路径上的最长边 for (i=1;i<=tot;++i) GetBest(i); for (delta=INF,i=1;i<=tot;++i){ //进行最小添删操作 if (pre[i] && dist[0][i]!=INF && dist[0][i]-best[i]<delta) { delta=dist[0][i] - best[i]; //若这个顶点的前一个点不是V0,并且删除最大边后的添删值小于delta,选择这个顶点并记录 choose=i; } } Reverse(choose); //维护树的形状 ktree+=delta; //ktree为此时度限制生成树的大小 if (ktree<ans) ans=ktree; } printf("Total miles driven: %d\n", ans); } int main(){ GetData(); Solve(); return 0; } Followed by: Post your reply here: |
All Rights Reserved 2003-2013 Ying Fuchen,Xu Pengcheng,Xie Di
Any problem, Please Contact Administrator