1 条题解

  • 0
    @ 2025-8-24 22:21:24

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar BingBang
    **

    搬运于2025-08-24 22:21:23,当前版本为作者最后更新于2020-11-26 16:09:07,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    本题解避易就难,提供一种基于最小费用最大流的解法。

    首先发现n,m100n,m \leq 100,最小费用最大流在合理的建图方式下时空复杂度正确。
    考虑建图方式:

    建一个“超级源点”ss,从ss向每个选手连接一条流量为11,费用为0.00.0的边,表示该点代表一名选手。
    对于每名选手xx,向每类比赛yy连接一条流量为11,费用为编号为xx的选手对第yy类比赛的知识积累程度的相反数的边,即费用为s[x][y]-s[x][y]。取相反数目的是在费用流运行过程中保证花费“最小”,这样求出的答案取相反数后就会“最大”。
    建一个“第一汇点”t1t_1,所有比赛向t1t_1连接一条流量为infinf(正无穷大),费用为0.00.0的边。
    最后建一个“第二汇点”t2t_2t1t_1t2t_2连接一条流量为kk,费用为0.00.0的边,表示派出kk名选手参赛。
    随后在图上运行最小费用最大流(源点ss,汇点t2t_2),得出的费用的相反数即为答案。

    正确性分析:

    最大流保证了kk名选手必定都会参加比赛,而在所有边的费用均为真实费用的相反数的图上运行最小费用流,得到的“最小费用”取相反数之后成为“最大费用”,即“最大知识积累程度”。

    参考代码:

    节点编号解释:

    起点:1
    选手:2~n+1
    比赛:n+2~n+m+1
    比赛汇合点:n+m+2
    终点:n+m+3 
    

    其它代码信息见注释。

    #include<bits/stdc++.h>
    using namespace std;
    #define re register
    inline int read(){
    	re int x=0,f=1;re char ch=getchar();
    	while(!(ch>='0'&&ch<='9')&&ch!='-')ch=getchar();
    	if(ch=='-')f=-1,ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return x*f;
    }
    void fput(int x){
    	if(x<0)putchar('-'),x=-x;
    	if(x>=10)fput(x/10);
    	putchar(x%10+'0');
    	return ;
    }
    #define N 305
    #define M 40005
    #define inf N
    int n,m,k,head[M],cnt=1;
    struct node{//链式前向星存储有向边,w表示流量,c表示费用 
    	int to,nxt,w;
    	double c;
    }a[M];
    inline void addedge(int u,int v,int w,double c){
    	a[++cnt]=node{v,head[u],w,c};head[u]=cnt;
    	a[++cnt]=node{u,head[v],0,-c};head[v]=cnt;
    	return ;
    }
    int s,t;
    //起点:1
    //选手:2~n+1
    //比赛:n+2~n+m+1
    //比赛汇合点:n+m+2
    //终点:n+m+3 
    double dis[N];
    queue<int>q;
    bool vis[M];
    const double eps=1e-6;
    int now[M];
    inline bool bfs(){//SPFA 
    	for(re int i=s;i<=t;i++){
    		dis[i]=DBL_MAX;now[i]=head[i];
    	}
    	dis[s]=0.0;
    	q.push(s);
    	while(!q.empty()){
    		re int x=q.front();q.pop();vis[x]=0;
    		for(re int i=head[x];i;i=a[i].nxt){
    			if(a[i].w&&dis[a[i].to]>dis[x]+a[i].c){
    				dis[a[i].to]=dis[x]+a[i].c;
    				if(!vis[a[i].to]){
    					q.push(a[i].to);
    					vis[a[i].to]=1;
    				}
    			}
    		}
    	}
    	return fabs(dis[t]-DBL_MAX)>eps;
    }
    double cost;
    int dfs(int x,int flow){//dinic
    	if(x==t||!flow)return flow;
    	re int add=0;vis[x]=1;
    	for(re int i=now[x];i;i=a[i].nxt){
    		now[x]=i;
    		if(!vis[a[i].to]&&a[i].w&&dis[a[i].to]==dis[x]+a[i].c){
    			re int res=dfs(a[i].to,min(flow,a[i].w));
    			if(res){
    				flow-=res;
    				add+=res;
    				a[i].w-=res;
    				a[i^1].w+=res;
    				cost+=a[i].c*res;
    				if(!flow)break;
    			}
    		}
    	}
    	vis[x]=0;
    	return add;
    }
    int main(){
    	n=read();m=read();k=read();
    	s=1;t=n+m+3;
    	for(re int i=1;i<=n;i++){//超级源点向选手连边 
    		addedge(s,i+1,1,0.0);
    	}
    	for(re int i=1;i<=m;i++){//选手与比赛之间连边 
    		for(re int j=1,id;j<=n;j++){
    			id=read();
    			re double c;
    			scanf("%lf",&c);
    			addedge(id+1,i+n+1,1,-c);
    		}
    		addedge(i+n+1,n+m+2,inf,0.0);//比赛向第一汇点连边 
    	}
    	addedge(n+m+2,n+m+3,k,0.0);//第一汇点向第二汇点连边 
    	re int ans=0;
    	while(bfs()){//最小费用最大流 
    		ans+=dfs(s,INT_MAX);
    	}
    	printf("%.1f",-cost);//答案取相反数,保留一位小数 
    	return 0;
    }
    
    • 1

    信息

    ID
    5528
    时间
    1000ms
    内存
    32MiB
    难度
    (无)
    标签
    递交数
    0
    已通过
    0
    上传者