2025省选模拟9

2025省选模拟9

致敬造数据的人

T1、 Delov 的 npy 们

AGC031E

这个数据范围看着就像网络流,但是建模非常困难,按照原题意无法建出能用网络流解决的图,所以就需要转化题意。

转化题意自然要考虑那些限制,对于一个限制 横坐标小于等于 $ a_i $​ 的特定时刻最多选 $ b_i $ 个 , 我们考虑将他转化为对于选择的点横坐标从左往右数第 $ b_i + 1 $ 个的横坐标必须 $ \gt a_i $ ,同理另外三种限制也可以这样转化成 从哪到哪第几个点的 横/纵 坐标 大于/小于 多少 的形式 。然后我们先分别考虑对于横坐标和纵坐标的限制,对于横坐标,如果我们知道一共选了多少个点,那么对于从小到大每个点,我们都可以知道他的横坐标范围,同理按照纵坐标排序后也可以知道每个点的纵坐标取值范围,我们就知道每个待选择点可不可以成为第 $ i $ 个 横/纵 坐标 ,那么选择一个点就相当于给他找一个横坐标排序后的位置和纵坐标排序后的位置,然后就可以直接上网络流了。

具体来说,我们枚举最终选了 $ k $ 个点,同时我们可以知道分别按横纵坐标排完序后每个点横纵坐标范围,我们从源点向每个横坐标连一个 $ (1,0) $ 的边,从每个纵坐标向汇点连一个 $ (1,0) $ 的边,然后将每个待选择点拆开,从 $ i_1 $ 向 $ i_2 $ 连一条 $ (1,v_i) $ 的边,对每个点 $ i $ ,从对于 $ i $ 合法的横坐标向 $ i_1 $ 连 $ (1,0) $ 的边,从 $ i_2 $ 向合法的纵坐标连 $ (1,0) $ 的边,直接跑最大费用流即可。

$ N \le 80 $ 只是网络流的极限,不是 $ Delov $ 的极限

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=1e6+10,inf=0x3f3f3f3f;
const ll mod=1e9+7,linf=0x7f3f3f3f3f3f3f3f;
inline ll read(){
	char c=getchar();ll x=0;bool f=0;
	while(!isdigit(c))f=c=='-'?1:0,c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
}
struct jj{
	int to,next,fl,w;
}bi[N<<1];
int n,m,cnt=1,hd[N],st,ed,dis[N],q[N],l,r,pre[N];
bool vis[N];
inline void add(int x,int y,int z,int w){
	bi[++cnt]={y,hd[x],z,w},hd[x]=cnt,bi[++cnt]={x,hd[y],0,-w},hd[y]=cnt;
}
inline bool spfa(){
	for(int i=0;i<=ed;++i)
		dis[i]=-linf,vis[i]=0;
	dis[st]=0,vis[st]=1;q[l=r=0]=st;
	while(l<=r){
		int x=q[l++];vis[x]=0;
		for(int i=hd[x];i;i=bi[i].next){
			int j=bi[i].to;
			if(bi[i].fl&&dis[j]<dis[x]+bi[i].w){
				pre[j]=i^1;dis[j]=dis[x]+bi[i].w;
				if(!vis[j])vis[j]=1,q[++r]=j;
			}
		}
	}
	return dis[ed]>0;
}
struct op{
	int x,y,v;
}man[N];
int L[N],R[N],U[N],D[N];
inline int sol(int k){
	cnt=1;st=0,ed=k*2+n*2+1;
	for(int i=0;i<=ed;++i)
		hd[i]=0;
	for(int i=1;i<=k;++i)
		add(st,i,1,0),add(k+i,ed,1,0);
	for(int i=1;i<=n;++i){
		add(k*2+i,k*2+n+i,1,man[i].v);
		for(int j=1;j<=k;++j){
			if(man[i].x>=L[j]&&man[i].x<=R[k-j+1])add(j,i+k*2,1,0);
			if(man[i].y>=D[j]&&man[i].y<=U[k-j+1])add(i+n+k*2,j+k,1,0);
		}
	}
	int ans=0;
	while(spfa()){
		int pp=linf;
		for(int i=ed;i;i=bi[pre[i]].to){
			pp=min(pp,bi[pre[i]^1].fl);
		}
		for(int i=ed;i;i=bi[pre[i]].to){
			bi[pre[i]].fl+=pp,bi[pre[i]^1].fl-=pp;ans+=bi[pre[i]^1].w*pp;
		}
	}
	return ans;
}
signed main(){
	// #ifndef ONLINE_JUDGE
	// freopen("a.in","r",stdin);
	// freopen("a.out","w",stdout);
	// #endif
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read();
	for(int i=1;i<=n;++i)
		man[i].x=read(),man[i].y=read(),man[i].v=read();
	for(int i=0;i<=n;++i)
		R[i]=U[i]=inf;
	m=read();
	st=0,ed=n+m+1;
	char s;
	for(int i=1,x,y;i<=m;++i){
		scanf(" %c",&s);x=read(),y=read();
		if(s=='L')L[y+1]=max(L[y+1],x+1);
		else if(s=='R')R[y+1]=min(R[y+1],x-1);
		else if(s=='D')D[y+1]=max(D[y+1],x+1);
		else U[y+1]=min(U[y+1],x-1);
	}
	for(int i=1;i<=n;++i){
		L[i]=max(L[i],L[i-1]),R[i]=min(R[i],R[i-1]),D[i]=max(D[i],D[i-1]),U[i]=min(U[i],U[i-1]);
	}
	int ans=0;
	for(int i=1;i<=n;++i){
		ans=max(ans,sol(i));
	}
	cout<<ans;
}

T2、 皮胚

CF1572D

部分分肯定直接上费用流。

然后我们考虑每次退流过程,也就是会退掉一个极长链,其实这条链的长度不会超过 $ n $ ,我们具体看这个极长链会长成什么样。

一开始是 $ n $ 个 0, 每个位置上 0 表示没有改变, 1 表示改变了,那么我们就是每次选择一个位置将他从 0 变成 1,我们不能从 1 变成 0 ,因为这个数以前出现过,所以一条极长链的长度 $ \le n $ ,所以选择一条边会影响最多 $ n-1 $ 条边,有用的边一定是权值前 $ 2 n K $ 条。然后就可以做了。

码(写的比较唐导致常数巨大,但又不想改了,建议别看)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=4e6+10,inf=0x3f3f3f3f;
const ll mod=1e9+7,linf=0x3f3f3f3f3f3f3f3f;
inline ll read(){
	char c=getchar();ll x=0;bool f=0;
	while(!isdigit(c))f=c=='-'?1:0,c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
}
struct jj{
	int to,next,fl,w;
}bi[N<<1];
int n,m,K,v[N],st,ed,cnt=1,hd[N],dis[N],q[N<<1],l,r,pre[N];
bool vis[N];
int cntt[N>>1];
inline void add(int x,int y,int z,int w){bi[++cnt]={y,hd[x],z,w},hd[x]=cnt,bi[++cnt]={x,hd[y],0,-w},hd[y]=cnt;}
unordered_map<int,bool> ma;
inline bool spfa(){
	for(auto i:ma)
		dis[i.fi]=dis[i.fi+m]=-linf,vis[i.fi]=vis[i.fi+m]=0;
	dis[ed]=-linf,vis[ed]=0;
	dis[st]=0,vis[st]=1;q[l=r=0]=st;
	while(l<=r){
		int x=q[l++];vis[x]=0;
		for(int i=hd[x];i;i=bi[i].next){
			int j=bi[i].to;
			if(bi[i].fl&&dis[j]<dis[x]+bi[i].w){pre[j]=i^1;dis[j]=dis[x]+bi[i].w;if(!vis[j])vis[j]=1,q[++r]=j;}
		}
	}
	return dis[ed]>0;
}
signed main(){
	// #ifndef ONLINE_JUDGE
	freopen("pp.in","r",stdin);
	freopen("pp.out","w",stdout);
	// #endif
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read(),K=read();st=(1<<n)<<1,ed=st+1;m=1<<n;
    K=min(K,m>>1);
	for(int i=0;i<m;++i)
		v[i]=read();
	for(int i=0;i<m;++i){
		for(int j=0;j<n;++j){
			if((i>>j)&1){
				++cntt[v[i]+v[i^(1<<j)]];
			}
		}
	}
	int mx=n*K*2,now=0,pos;
	for(pos=2e6;pos>=1;--pos){
		if(now+cntt[pos]>=mx)break;
		now+=cntt[pos];
	}
	for(int i=0;i<m;++i){
		for(int j=0;j<n;++j){
			if((i>>j)&1){
				if(v[i]+v[i^(1<<j)]>pos){
					add(i,(i^(1<<j))+m,1,v[i]+v[i^(1<<j)]),add(i^(1<<j),i+m,1,v[i]+v[i^(1<<j)]);
					ma[i]=ma[i^(1<<j)]=1;
				}
			}
		}
	}
	for(auto i:ma){
		add(st,i.fi,1,0),add(i.fi+m,ed,1,0);
	}
	int ans=0,zh=0;
	for(int i=1;i<=K*2;++i){
		if(!spfa())break;
		++zh;
		for(int i=ed;i!=st;i=bi[pre[i]].to){
			bi[pre[i]].fl=1,bi[pre[i]^1].fl=0;ans+=bi[pre[i]^1].w;
		}
	}
	cout<<(ans+(K*2-zh)*pos)/2;
}

T3、

CF708D

我们考虑如何调整网络来做到流量平衡。

对于每条边分类讨论:

对于 $ f \le c $ 的边:

如果我们要退流的话,就从 $ v $ 向 $ u $ 连一条 $ (f,1) $ 的边。

如果要继续流的话,分为两段,第一段 $ f' \le c $ ,就连一条 $ u \to v \ \ (c-f,1) $ 的边,第二段 $ f' \le c $ 连一条 $ u \to v \ \ (inf,2) $ 的边。(感觉就是阶梯收费)

对于 $ f \gt c $ 的边:

我们首先把 $ c $ 提高到和 $ f $ 相等的水平,也就是直接给答案加上 $ f-c $ ,然后如果我们要退流的话,分为两段。第一段 $ f' \ge c $ ,那么相当于我的 $ c $ 其实一开始只加到 $ f' $ 即可,然后把 $ f $ 降低到 $ f' $ 总费用还是 $ f-c $ ,所以连 $ v \to u \ \ (f-c,0) $ 的边,然后第二段 $ 0 \le f' \lt c $ ,连 $ v \to u \ \ (c,1) $ 的边 , 如果要加流的话,就连 $ u \to v \ \ (inf,2) $ 的边。

同时我们用 $ in_i $ 表示 $ i $ 点的入度与出度的差,如果 $ in_i \ge 0 $ ,说明我们还要流出去 $ in_i $ 的流量才行,所以新建超级源点 $ S $ ,从 $ S $ 向 $ i $ 连 $ (in_i , 0) $ 的边,同理对于 $ in_i \lt 0 $ 的点,从超级汇点向 $ i $ 连 $ (-in_i , 0 ) $ 的边。但是其实对于原图的源点和汇点我们并不关心他的出度入度是否相等,但是上面的建模让我们不得不考虑这个,所以我们要连一条 $ n \to 1 \ \ (inf,0) $ 的边来抵消贡献。

关于为什么是要连一条 $ n \to 1 \ \ (inf,0) $ 的边,而不是直接把 $ 1,n $ 关于超级源点汇点的边删掉,我不太清楚,但是我感觉是,原本这个图一定有 $ in_i \lt 0 $ 的边就一定有 $ in_i \gt 0 $ 的边,那么我们删掉的那两条边在这里面就起着从超级源点接受流量或向超级汇点传送流量的作用,如果少了这两条边就会导致流量与原来不同,所以不能删去。关于这儿讲的很含糊,如果有清楚地可以告诉我。(upd: 此处的理解的确是错的,可以用有源汇上下界可行流解释通,感谢 Qyun 和 int_R 帮忙改正错误)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=1e6+10,inf=0x3f3f3f3f;
const ll mod=1e9+7,linf=0x3f3f3f3f3f3f3f3f;
inline ll read(){
	char c=getchar();ll x=0;bool f=0;
	while(!isdigit(c))f=c=='-'?1:0,c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
}
struct jj{
	int to,next,fl,w;
}bi[N<<1];
int n,m,cnt=1,hd[N],dis[N],pre[N],st,ed,in[N],q[N],l,r;
bool vis[N];
inline void add(int x,int y,int z,int w){
	bi[++cnt]={y,hd[x],z,w},hd[x]=cnt,bi[++cnt]={x,hd[y],0,-w},hd[y]=cnt;
}
inline bool spfa(){
	for(int i=0;i<=ed;++i)
		dis[i]=inf,vis[i]=0;
	dis[st]=0,vis[st]=1;q[l=r=0]=st;
	while(l<=r){
		int x=q[l++];vis[x]=0;
		for(int i=hd[x];i;i=bi[i].next){
			int j=bi[i].to;
			if(bi[i].fl&&dis[j]>dis[x]+bi[i].w){dis[j]=dis[x]+bi[i].w;pre[j]=i^1;if(!vis[j])vis[j]=1,q[++r]=j;}
		}
	}
	return dis[ed]!=inf;
}
signed main(){
	// #ifndef ONLINE_JUDGE
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	// #endif
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read(),m=read();st=0,ed=n+1;
	add(n,1,inf,0);
	int ans=0;
	for(int i=1,x,y,i1,i2;i<=m;++i){
		x=read(),y=read(),i1=read(),i2=read();
		if(i1<=i2){
			ans+=i2-i1;add(y,x,i2-i1,0);add(y,x,i1,1);
		}
		else{
			add(y,x,i2,1),add(x,y,i1-i2,1);
		}
		add(x,y,inf,2);
		in[y]+=i2,in[x]-=i2;
	}
	for(int i=1;i<=n;++i){
		if(in[i]>=0)add(st,i,in[i],0);
		else add(i,ed,-in[i],0);
	}
	while(spfa()){
		int man=inf;
		for(int i=ed;i;i=bi[pre[i]].to){
			man=min(man,bi[pre[i]^1].fl);
		}
		for(int i=ed;i;i=bi[pre[i]].to){
			bi[pre[i]].fl+=man,bi[pre[i]^1].fl-=man;ans+=man*bi[pre[i]^1].w;
		}
	}
	cout<<ans;
}
posted @ 2025-02-05 20:28  lzrG23  阅读(50)  评论(5编辑  收藏  举报