2025洛谷省选模拟赛D2T2“flower” 题解

2025洛谷省选模拟赛D2T2“flower” 题解

这就是一个二分图最大权完美匹配,用费用流可以解决,期望 35 分。建了 O(n2) 条边。

因为费用流的复杂度为 O(n×m×maxflow),可以考虑减少图的边数以减小复杂度。

一个函数有多种表示形式,我们应尽量选择与算法相匹配的。

因为 |ab|=max(ab,ba)。我们将权值拆为四种形式:

  • (li+wi)(lj+wj)

  • (wili)+(ljwj)

  • (liwi)+(wjlj)

  • (li+wi)+(lj+wj)

相当于是找这四项的最大值,这与我们”最大费用最大流“的算法刚好相符,并且这样贡献独立。

具体可以新建 4 个点 V1,V2.V3.V4,对于每个蓝/红花按顺序向其连边,边数变为了 O(n) 由于 maxflow=n,复杂度 O(n3)。由于:

网络流可行数据范围往 109

可以得 55 分。

0l,w2,总共只有 3×3×2=18 中不同的点,直接计数+改容量可以通过。

可以得 70 分。

下文中把代表每个花朵的左部点称作 x,右部点称作 y,源点称作 S,汇点称作 T,中间的新增点称作 V。左部点向 V 的连边的的边权记作 E(x,V),右部点的记作 E(V,y)

发现这样建图有一个特点那就是 V 很少只有四个,且 x,y 很多,但 V 是每次增广的必经点于是考虑模拟费用流。

模拟费用流要对于一次增广考虑其性质。

  • 发现原最大流量为 n 但每次增广的流量恰好为 1。所以其本质是找了 n 次最长路。

  • 那么每次增广的路径一定经过 SVVT。如果我们能不考虑 x,y 直接在 S,V,T 之间转移,那么点数会减少很多。

所以我们考虑 S,V,T 之间的转移,设 q[A][B] 表示 AB 的转移,将这些转移分为四类:

  • SxV:即 S 经过左部点到 V,这是简单的,根据增广的过程,一定会选择一个没有被选择的左部点 x 进行增广,我们直接在 q[S][V] 中维护这些 E(x,V)

  • VyT:即 V 经过右部点到 T,同理也一定会选择一个没有没选择过的右部点 y,在 q[V][T]中维护这些 E(V,y)

  • V1yV2V1 经过右部点到 V2,这是一个退流操作:其本质就是将 V2y 变为了 V1y 所以转移边权应该是 E(V1,y)E(V2,y)

  • V1xV2V1 经过左部点 x 到达 x,这也是一个推流操作,本质是将 xV1 变为了 xV2,所以转移边权应该是 E(x,V2)E(x,V1)

下面是第三,第四种转移的图示。

那么对于一次最长路,我们只需要 A,B 选出 q[A][B] 中的最大值即可。建图跑 SPFA 即可。

在选出一次后,我们应该对于最长路径上的所有 x,y ,更新出其实际所连的 V 以达成懒惰删除的目的,因为 SV,VV,VT 的边权都及其依赖 x,y 的实际链接情况。

对于一条更新的连边 xVi,我们应该 j 连边 ViVj,记录相应边权 E(x,Vj)E(x,Vi)

对于一条更新的连边 Viy,我们应该 j 连边 VjVi,记录相应边权 E(Vj,y)E(Vi,y)

由于一次最多添加 3×4×5=60 条边,而一次SPFA的计算次数最大为 6×62=216

最后复杂度(大致估计常数) O(36n+60nlog60n),去掉常数后就是 O(nlogn)

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> pa;
const int NN=4e5+5,INF=0x3f3f3f3f3f3f3f3f;
int n;
int d[10][10],id[10][10];
priority_queue<pa> q[10][10];//第1维是边权,第2维是转移过程点 
int match[NN],s=5,t=6,a[NN][10],ans;
bool rit[NN];//标记为右侧的点 
int dis[10],pre[10];
bool vst[NN];

int SPFA(){
	memset(dis,0xcf,sizeof dis);//找最长路 
	queue<int> Q;
	Q.push(s);dis[s]=0;vst[s]=1;
	while(!Q.empty()){
		int x=Q.front();Q.pop();
		vst[x]=0;
		for(int y=1;y<=6;y++){
			if(dis[y]>=dis[x]+d[x][y])continue;
			dis[y]=dis[x]+d[x][y];
			pre[y]=x;
			if(!vst[y]){
				Q.push(y);
				vst[y]=1;
			}
		}
	}
	return dis[t];
}
signed main(){
	cin>>n;
	for(int i=1;i<=n<<1;i++){
		int c,l,w;cin>>c>>l>>w;
		if(!c){
			a[i][1]=l+w,a[i][2]=w-l,a[i][3]=l-w,a[i][4]=-l-w;
			for(int j=1;j<=4;j++)q[s][j].push({a[i][j],i});
		}else{
			rit[i]=1;
			a[i][1]=-l-w,a[i][2]=l-w,a[i][3]=w-l,a[i][4]=l+w;
			for(int j=1;j<=4;j++)q[j][t].push({a[i][j],i});
		}
	}
	for(int cs=1;cs<=n;cs++){//每次网络流都增加1的流量,那么总共恰好时找n次最长路 
		//更新一下边权
		for(int i=1;i<=6;i++){
			for(int j=1;j<=6;j++){
				d[i][j]=-INF;
				while(!q[i][j].empty()){
					int u=q[i][j].top().second;//注意这里不能pop因为若本次没有使用这条边,其还是要保留的 
					bool flag=0;//检验这条边是否被删除,相当于一个懒惰删除 
					if(!rit[u]){//这是一个左部点 
						if(i==s)flag|=!match[u];//相当于新开一条流 
						else flag|=match[u]==i;//相当于,对于一个V,要求其真的指向自己 
					}else{//这是一个右部点 
						if(j==t)flag|=!match[u];
						else flag|=match[u]==j;
					}
					if(!flag)q[i][j].pop();
					else{
						id[i][j]=u;
						d[i][j]=q[i][j].top().first;
						break;
					}
				}
				
			}
		}
		ans+=SPFA();//跑最长路 
		//更新边权 
		for(int i=t;i!=s;i=pre[i]){//遍历路径 
			int u=id[pre[i]][i];
			if(!rit[u])match[u]=i;
			else match[u]=pre[i];
			for(int j=1;j<=4;j++){
				if(!rit[u])q[match[u]][j].push({a[u][j]-a[u][match[u]],u});
				else		 q[j][match[u]].push({a[u][j]-a[u][match[u]],u});
			}
		}
	}
	cout<<ans;
	return 0;
}

作者:lupengheyyds

出处:https://www.cnblogs.com/lupengheyyds/p/18742117

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   lupengheyyds  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示