【Atcoder Grand Contest 043】【AGC043】F - Jewelry Box(对偶原理)(费用流)

传送门


做法大概是官方题解,叙述和变量定义略有不同。

题解:

首先考虑解决一组询问,设询问参数为 A A A

那么考虑在每家店中选择了 A A A 件物品,怎么验证能否满足限制。
稍微用一下脑子就会发现,直接对每家店选出来的物品按 S S S 排序,然后按顺序分别放到各个盒子里面,直接 c h e c k check check 每个盒子是否满足条件,如果不满足,则不存在任何一种分配方案满足条件,证明就是xjb贪心,这里就不赘述了。

以下讨论建立在每家店的物品已经按照 S i , j S_{i,j} Si,j 排好序的基础上。

x i , j x_{i,j} xi,j 表示第 i i i 家店前 j j j 件物品一共选择了多少个,即选择物品数量的前缀和。

对于限制 ( u , v , w ) (u,v,w) (u,v,w) ,根据上面的验证方式,显然我们可以转化为限制:
对于 1 ≤ j ≤ K v 1\leq j\leq K_v 1jKv,设 k k k 是满足 S u , k + w ≥ S v , j S_{u,k}+w\geq S_{v,j} Su,k+wSv,j 的最小的 k k k,如果不存在,则 k = K u + 1 k=K_u+1 k=Ku+1,上述限制等价于 x u , k − 1 ≤ x v , j − 1 x_{u,k-1}\leq x_{v,j-1} xu,k1xv,j1

容易发现这个问题是个线性规划。

m i n i m i z e : ∑ P i , j ( x i , j − x i , j − 1 ) s . t x i , j ≤ x i , j + 1 x i , j + 1 ≤ x i , j + C i , j + 1 x u , k ≤ x v , j x i , 0 = 0 x i , K i = A x i , j ≥ 0 \begin{aligned} minimize:&&&&&&\sum &P_{i,j}(x_{i,j}-x_{i,j-1})\\ s.t&&&&&&x_{i,j}&\leq x_{i,j+1}\\ &&&&&&x_{i,j+1}&\leq x_{i,j}+C_{i,j+1}\\ &&&&&&x_{u,k}&\leq x_{v,j}\\ &&&&&&x_{i,0}&=0\\ &&&&&&x_{i,K_i}&=A\\ &&&&&&x_{i,j}&\geq 0 \end{aligned} minimize:s.txi,jxi,j+1xu,kxi,0xi,Kixi,jPi,j(xi,jxi,j1)xi,j+1xi,j+Ci,j+1xv,j=0=A0

由于数据范围很小,如果只有一组询问的话可以直接上单纯形。

容易发现系数矩阵是个全幺模矩阵,考虑利用网络流解决。

转标准一点,同时注意到求值和限制的各个地方只和差有关,可以不管 x i , 0 = 0 x_{i,0}=0 xi,0=0 x i , K i = A x_{i,K_i}=A xi,Ki=A 的限制,转而考虑 x i , K i − x i , 0 = A x_{i,K_i}-x_{i,0}=A xi,Kixi,0=A

注意我们之后显然要进行一次对偶,要求对偶后的限制为 ≤ \leq 才好建网络流,所以我们将限制转为 ≥ \geq 再说。

m i n i m i z e : ∑ x i , j ( P i , j − P i , j + 1 ) s . t . x i , j + 1 − x i , j ≥ 0 x i , j − x i , j + 1 ≥ − C i , j + 1 x v , j − x u , k ≥ 0 x i , 0 − x i , k i ≥ − A x i , K i − x i , 0 ≥ A x i , j ≥ 0 \begin{aligned} minimize:&&&&&&\sum &x_{i,j} (P_{i,j}-P_{i,j+1})\\ s.t.&&&&&&x_{i,j+1}-x_{i,j}&\geq 0\\ &&&&&&x_{i,j}-x_{i,j+1}&\geq -C_{i,j+1}\\ &&&&&&x_{v,j}-x_{u,k}&\geq 0\\ &&&&&&x_{i,0}-x_{i,k_i}&\geq -A\\ &&&&&&x_{i,K_i}-x_{i,0}&\geq A\\ &&&&&&x_{i,j}&\geq 0 \end{aligned} minimize:s.t.xi,j+1xi,jxi,jxi,j+1xv,jxu,kxi,0xi,kixi,Kixi,0xi,jxi,j(Pi,jPi,j+1)0Ci,j+10AA0

对偶之后是个最大费用可行流问题。

由于所有限制都是两个变量一加一减,所以可以不用把对偶后的形式写出来,直接建图。

对偶前的变量为点,限制为边,限制 x − y ≥ w x-y\geq w xyw 在对偶后是一条 x x x 连向 y y y ,容量为 I N F INF INF,费用为 w w w 的边。而对于原最小化式子中 x x x 的系数 c o e f x coef_x coefx 为正表示 x x x 可以无费用补的流量,为负表示 x x x 可以无费用流失的流量,一般来说应该是正的从源点 S S S 补过来,负的流向汇点 T T T,不过这道题因为图的特殊性,可以直接从 x i , j − 1 x_{i,j-1} xi,j1 x i , j x_{i,j} xi,j 连边 ( P i , j , 0 ) (P_{i,j},0) (Pi,j,0)

边权乘个 − 1 -1 1 考虑最小费用可行流,由于没有容量下界问题,显然只需要考虑负环。

乘个 − 1 -1 1 之后负边权只有原限制中 x i , K i − x i , 0 ≥ A x_{i,K_i}-x_{i,0}\geq A xi,Kixi,0A,而 x i , 0 − x i , K i ≥ − A x_{i,0}-x_{i,K_i}\geq -A xi,0xi,KiA 根本不会纳入考虑,是个废边,于是我们只需要考虑 A A A 分段的时候的流量和流过的原图中的费用,即在删掉这些边之后只考虑剩下的图中比 A A A 小的增广路径。

容易注意到 x i , j + 1 − x i , j ≥ 0 x_{i,j+1}-x_{i,j}\geq 0 xi,j+1xi,j0 也是一个废限制,不用把边建立出来。

于是建立忽略 x i , K i − x i , 0 ≥ A x_{i,K_i}-x_{i,0}\geq A xi,Kixi,0A 这个限制的边,跑最小费用可行流,把费用和流量存下来,二分找到对应答案即可。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}template<typename T>T get_integer(){
		char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';T x=c^48;
		while(isdigit(c=gc()))x=((x+(x<<2))<<1)+(c^48);return f?-x:x;
	}inline int gi(){return get_integer<int>();}
	inline ll gl(){return get_integer<ll>();}
}using namespace IO;

using std::cerr;
using std::cout;
using pll=std::pair<ll,ll>;
#define fi first
#define se second

cs ll INF=1ll<<60;

int Src,Snk,ct;

namespace NetWork{

cs int N=1e3+7;

struct edge{int to,rev;ll cap,w;};
typedef std::vector<edge>::iterator iter;
std::vector<edge> G[N];iter cur[N];
inline void adde(int u,int v,ll cap,ll cost){
	G[u].push_back({v,(int)G[v].size(),cap,cost});
	G[v].push_back({u,(int)G[u].size()-1,0,-cost});
}

ll dis[N];bool vs[N];

bool SPFA(){
	memset(dis,0x3f,sizeof dis);
	memset(vs,0,sizeof vs);
	std::deque<int> q({Src});dis[Src]=0;
	while(!q.empty()){
		int u=q.front();q.pop_front();
		vs[u]=false;cur[u]=G[u].begin();
		for(cs edge &e:G[u])
		if(e.cap&&dis[e.to]>dis[u]+e.w){
			dis[e.to]=dis[u]+e.w;
			if(!vs[e.to]){
				if(!q.empty()&&dis[e.to]<dis[q.front()])
					q.push_front(e.to);
				else q.push_back(e.to);
				vs[e.to]=true;
			}
		}
	}return dis[Snk]<INF;
}

ll tot_flow,tot_cost;
ll dfs(int u,ll flow){
	if(u==Snk){tot_flow+=flow;return flow;}
	ll ans=0;vs[u]=true;
	for(iter &e=cur[u];e!=G[u].end();++e)
	if(e->cap&&dis[e->to]==dis[u]+e->w&&!vs[e->to]){
		ll dlt=dfs(e->to,std::min(flow-ans,e->cap));
		e->cap-=dlt;G[e->to][e->rev].cap+=dlt;
		vs[e->to]=dlt==0;if((ans+=dlt)==flow)break;
	}vs[u]=false;return ans;
}

std::vector<pll> Flow(){
	std::vector<pll> p;
	while(SPFA()){
		tot_flow=0;dfs(Src,INF);
		p.push_back({dis[Snk],tot_flow});
		if(tot_flow>(1ll<<50))break;
	}return p;
}

}

cs int N=35;
int id[N][N],cnt[N];
int S[N][N],P[N][N];
ll C[N][N];

void Main(){
	int n=gi();
	for(int re i=1;i<=n;++i){
		cnt[i]=gi();
		for(int re j=1;j<=cnt[i];++j)
			S[i][j]=gi(),P[i][j]=gi(),C[i][j]=gl();
		for(int re j=1;j<=cnt[i];++j)
			for(int re k=j+1;k<=cnt[i];++k)
			if(S[i][j]>S[i][k]){
				std::swap(S[i][j],S[i][k]);
				std::swap(P[i][j],P[i][k]);
				std::swap(C[i][j],C[i][k]);
			}
	}Src=0,Snk=ct=1;
	for(int re i=1;i<=n;++i){
		for(int re j=1;j<=cnt[i]-1;++j)
			id[i][j]=++ct;
		id[i][0]=Src,id[i][cnt[i]]=Snk;
		for(int re j=1;j<=cnt[i];++j){
			NetWork::adde(id[i][j-1],id[i][j],P[i][j],0);
			NetWork::adde(id[i][j-1],id[i][j],INF,C[i][j]);
		}
	}
	int m=gi();
	while(m--){
		int u=gi(),v=gi(),w=gi();
		for(int re j=1,k=1;j<=cnt[v];++j){
			for(;k<=cnt[u]&&S[u][k]+w<S[v][j];++k);
			NetWork::adde(id[v][j-1],id[u][k-1],INF,0);
		}
	}
	auto p=NetWork::Flow();m=gi();
	std::vector<pll> q;ll x=0,y=0;
	for(auto t:p){
		q.push_back({x,y});
		x+=t.se,y+=t.fi*t.se;
	}
	while(m--){
		ll A=gl();
		size_t i=std::lower_bound(p.begin(),p.end(),(pll){A,-INF})-p.begin();
		if(i==p.size())cout<<"-1\n";
		else cout<<(q[i].fi*A-q[i].se)<<"\n";
	}
}

inline void file(){
#ifdef zxyoi
	freopen("F.in","r",stdin);
#endif
}signed main(){file();Main();return 0;} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值