poj1639 Picnic Planning(度限制最小生成树)

给定最多20个点的无向图,要求1号点度不能超过k,让你求出它的最小生成树。
大概思路就是先把与1号点相连的边忽略,原图分成若干个连通块,假设分成了m个块,在每个连通块内求最小生成树。现在1号点的度至少为m,因此显然如果m>k是无解的。我们现在把这些连通块用m条1出发的边连起来,我们每一块选一条最小的边去连。现在我们就得了m度限制最小生成树。我们还可以把m度限制变成m+1…k限制,选一个最小的即可。
怎么由m限制最小生成树得到m+1限制最小生成树呢?
我们肯定是新加进一条与1相连的边,假设我们新加入了(1,x)这条边,那么我们就得到了一个环,需要删掉一条边,显然我们会删掉最大的那条边,也就是在原树上x到1的路径上最大的边。这个东西可以O(n)预处理一下。然后做了这个替换后的改进是dp[i]-w[1,x]。如果改进值小于等于0就可以break了。否则一直改到不能改即可。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <utility>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 30
#define pa pair<int,int>
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,m=0,mp[N][N],fa[N],dp[N],K,ans=0,k=0,op[N],del[N];
bool exist[N][N];pa ed[N];
map<string,int>id;
struct edge{
    int x,y,val;
}e[N*N];
inline bool cmp(edge x,edge y){return x.val<y.val;}
inline void add(int x,int y,int val){e[++m].x=x;e[m].y=y;e[m].val=val;}
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
inline void dfs(int x){
    for(int y=1;y<=n;++y){
        if(!exist[x][y]||y==fa[x]) continue;
        fa[y]=x;if(mp[x][y]>dp[x]) dp[y]=mp[x][y],ed[y]=make_pair(x,y);
        else dp[y]=dp[x],ed[y]=ed[x];dfs(y);
    }
}
inline bool solve(){
    memset(dp,0,sizeof(dp));memset(fa,0,sizeof(fa));
    dfs(1);memset(del,0,sizeof(del));int mx=0;
    for(int i=2;i<=n;++i){
        if(exist[1][i]||mp[1][i]==inf) continue;
        del[i]=dp[i]-mp[1][i];if(del[i]>del[mx]) mx=i;
    }if(!mx) return 0;exist[1][mx]=1;int x=ed[mx].first,y=ed[mx].second;
    exist[x][y]=exist[y][x]=0;ans-=del[mx];return 1;
}
int main(){
//  freopen("a.in","r",stdin);
    int owo=read();id["Park"]=1;n=1;memset(mp,inf,sizeof(mp));
    while(owo--){
        string s1,s2;cin>>s1>>s2;
        if(!id[s1]) id[s1]=++n;if(!id[s2]) id[s2]=++n;
        mp[id[s1]][id[s2]]=mp[id[s2]][id[s1]]=min(mp[id[s1]][id[s2]],read());
    }K=read();
    for(int i=2;i<=n;++i)
        for(int j=i+1;j<=n;++j)
            if(mp[i][j]!=inf) add(i,j,mp[i][j]);
    sort(e+1,e+m+1,cmp);for(int i=1;i<=n;++i) fa[i]=i;k=n-1;
    for(int i=1;i<=m;++i){
        int xx=find(e[i].x),yy=find(e[i].y);
        if(xx==yy) continue;fa[xx]=yy;--k;ans+=e[i].val;exist[e[i].x][e[i].y]=exist[e[i].y][e[i].x]=1;
    }memset(dp,inf,sizeof(dp));memset(op,0,sizeof(op));
    for(int i=2;i<=n;++i){
        if(mp[1][i]<dp[find(i)]) dp[fa[i]]=mp[1][i],op[fa[i]]=i;
    }for(int i=2;i<=n;++i){
        if(fa[i]!=i) continue;ans+=dp[i];exist[1][op[i]]=1;
    }while(k<K){
        ++k;if(!solve()) break;
    }printf("Total miles driven: %d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值