ZJOI 2011 营救皮卡丘

营救皮卡丘

题意

1.n个据点,m条双向路
2.K个人从0点出发
3.如果要摧毁K号据点,必须先摧毁1- (K-1)号据点
即必须按顺序摧毁据点
4.在K-1被摧毁先不能经过K号据点
5.经过K点即摧毁K点,可以反复经过
6.K个人分头行动
7.目标摧毁N号节点,并使K个人经过的道路长度和最小

P40

dp[i][j]:破坏了第i个据点后,每个人在j(状压位置)的经过的最少路径和

Pac

这题是最小费用流

具体流程

1.因为要摧毁所有据点,可以看成覆盖每个点
2.因为要路径和最小,可以看成费用最少
3.在保证覆盖所有点的情况下,费用最小
最大流的情况下,最小费-->最小费用流
4.首先把一个点拆成两个,一个入点,一个出点
每个点只能被进出一次

建图

1.S->0有一条0费K流的路
2.i->j有一条dis[i][j]费1流的路(每条路走一次)
dis[i][j]是不经过大于i,j的点的情况小从i->j的最短路
3.每个点都向T连一条0费1流的路
每个点出去一次
4.每个点都想S连一条0费1流的路
每个点进入一次

注意floyd求最短路时if(k>i&&k>j)continue;不能用经过比i,j都大的点

具体代码

#include<bits/stdc++.h>
using namespace std;
const int M=505;
int n,m,K;
int dis[M][M];
int S,T;
int asdf,head[M];
struct edge {
    int to,nxt,flow,cost;
} G[M*M*2];
void add_edge(int a,int b,int c,int f) {
    G[++asdf].to=b;
    G[asdf].nxt=head[a];
    G[asdf].cost=c;
    G[asdf].flow=f;
    head[a]=asdf;
}
void ADD(int a,int b,int c,int f) {
    add_edge(a,b,c,f);
    add_edge(b,a,-c,0);
}
void floyd() {
    for(int k=0; k<=n; k++) {
        for(int i=0; i<=n; i++) {
            for(int j=0; j<=n; j++) {
                if(k>i&&k>j)continue;
                if(dis[i][k]+dis[k][j]<dis[i][j]) {
                    dis[i][j]=dis[i][k]+dis[k][j];
                }
            }
        }
    }
}
int pre[M],last[M],Dis[M],flow[M];
bool mark[M];
bool spfa() {
    queue<int>Q;
    memset(mark,0,sizeof(mark));
    memset(Dis,63,sizeof(Dis));
    memset(flow,63,sizeof(flow));
    Q.push(S);
    mark[S]=1;
    Dis[S]=0;
    while(!Q.empty()) {
        int x=Q.front();
        Q.pop();
        mark[x]=0;
        for(int i=head[x]; i; i=G[i].nxt) {
            int y=G[i].to;
            if(G[i].flow>0&&Dis[y]>Dis[x]+G[i].cost) {
                Dis[y]=Dis[x]+G[i].cost;
                pre[y]=i;
                last[y]=x;
                flow[y]=min(flow[x],G[i].flow);
                if(!mark[y]) {
                    mark[y]=1;
                    Q.push(y);
                }
            }
        }
    }
    return Dis[T]<=1e9;
}
int solve() {
    int mincost=0,maxflow=0;
    while(spfa()) {
        mincost+=flow[T]*Dis[T];
        maxflow+=flow[T];
        for(int i=T; i!=S; i=last[i]) {
            G[pre[i]].flow-=flow[T];
            G[pre[i]^1].flow+=flow[T];
        }
    }
    return mincost;
}
int main() {
    int a,b,c;
    scanf("%d %d %d",&n,&m,&K);
    memset(dis,63,sizeof(dis));
    for(int i=0; i<=n; i++)dis[i][i]=0;
    for(int i=1; i<=m; i++) {
        scanf("%d %d %d",&a,&b,&c);
        dis[a][b]=min(dis[a][b],c);
        dis[b][a]=dis[a][b];
    }
    S=(n+1)*2,T=(n+1)*2+1;
    asdf=1;
    ADD(S,0,0,K);
    for(int i=1; i<=n; i++) {
        ADD(S,i,0,1);
        ADD(i+n+1,T,0,1);
    }
    floyd();
    for(int i=0; i<n; i++) {
        for(int j=i+1; j<=n; j++) {
            if(dis[i][j]<=1e9)ADD(i,j+n+1,dis[i][j],1);
        }
    }
    printf("%d\n",solve());
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值