P14863 [ICPC 2021 Yokohama R] Distributing the Treasure

洛谷

非常神的构造。

首先发现 \(m\le n\) 的情况是白给的,直接一个人最多给一个就可以了。

那么在 \(m>n\) 的情况中,\(n\) 的范围就很小了。

\(v(A,S_A)\) 表示 \(A\) 这个人选择了集合 \(S_A\) 的价值。

先考虑 \(n=2\) 的情况,如果我们从大到小加,那么会发现一个惊人的事情,在原本集合合法的情况下,假如我要加入 \(S_A\) 中就需要满足条件 \(v(B,S_A)\le v(B,S_B)\)

但是如果两边都不满足的话怎么办?

因为两个人都觉得自己比对面少,那么直接让两个人交换即可。

同理我们推广到不止两个人的情况。

那么一个加入想要满足条件,需要加入的 \(S_A\) 满足对于任意一个人 \(X\) 满足 \(v(X,S_A)\le v(X,S_X)\)

考虑如何交换,此时我们希望局面可以保证存在一个人处于可以增加一个数字的状态。

感性感受一下,我们分为了两组,并且将人和集合构成了二分图,每个人都希望自己拥有的这个集合在自己眼中价值较高。

发现这很像二分图最大权匹配。

考虑一下是否正确,如果不存在合法情况,如果 \(X\) 认为 \(Y\) 的比自己好,那么从 \(X\)\(Y\) 连接一条边。

由于不合法,每个点的入度至少为 \(1\),因此必然存在环。

按照环的顺序交换,就会得到更大的权值,因此最大权匹配必然合法

得到了这个关键性质,我们只需要得到最大权即可。

直接跑常规算法比较慢,因为我们只修改了一个集合,修改必定包含这个集合,所以把到达这个点的边去掉,形成一个有向无环图,进行拓扑排徐求距离,并且最后处理时加上到达修改集合的点的权值即可。

由于只有一个集合修改,这个集合被新的点拿走,新的点原本的集合又被后面的点拿走,以此类推,最后还是形成了一个环形的过程,因此要找的就是最大权环,并且找一次即可。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,id[505],d[505],val[505][505],dis[505],pre[505],ans[200005];
vector<int> S[505],e[505];
vector<int> a[505];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	if(n>=m){
		for(int i=1;i<=m;i++)cout<<i<<' ';
		return 0;
	}
	for(int i=1;i<=n;i++)a[i].resize(m+5);
	for(int i=1;i<=n;i++)id[i]=i;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>a[i][j];
	for(int t=1;t<=m;t++){
		int w;
		for(int i=1;i<=n;i++){
			bool fl=0;
			for(int j=1;j<=n;j++)if(val[j][id[j]]<val[j][id[i]]){
				fl=1;
				break;
			}
			if(!fl){
				w=i;
				break;
			}
		}
		S[id[w]].push_back(t);
		for(int i=1;i<=n;i++)val[i][id[w]]+=a[i][t],e[i].clear(),d[i]=pre[i]=0,dis[i]=-1e16;
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(j!=w&&val[i][id[i]]<val[i][id[j]])e[i].push_back(j),d[j]++;
		queue<int> q;
		dis[w]=0;
		for(int i=1;i<=n;i++)if(!d[i])q.push(i);
		while(!q.empty()){
			int u=q.front();
			q.pop();
			for(int v:e[u]){
				if(dis[v]<dis[u]-val[u][id[u]]+val[u][id[v]]){
					dis[v]=dis[u]-val[u][id[u]]+val[u][id[v]];
					pre[v]=u;
				}
				d[v]--;
				if(!d[v])q.push(v);
			}
		}
		int w2=0;int ma=-1e16;
		for(int i=1;i<=n;i++)if(val[i][id[i]]<val[i][id[w]]&&ma<dis[i]-val[i][id[i]]+val[i][id[w]])ma=dis[i]-val[i][id[i]]+val[i][id[w]],w2=i;
		if(ma>=0)for(int p=w2,tmp=id[w];p;p=pre[p])swap(tmp,id[p]);
	}
	for(int i=1;i<=n;i++)for(int j:S[id[i]])ans[j]=i;
	for(int i=1;i<=m;i++)cout<<ans[i]<<' ';
	return 0;
}
posted @ 2026-07-08 22:16  huhangqi  阅读(3)  评论(0)    收藏  举报