【学习笔记】[AGC043F] Jewelry Box

点击看题意

Part 1

前置知识: L P LP LP对偶费用流。

考虑这样一个费用流:每条边 u v uv uv的流量设为 f u v f_{uv} fuv,容量设为 c u v c_{uv} cuv,费用设为 w u v w_{uv} wuv。 b u b_u bu设为流出-流入 。要求 m i n i m i z e ∑ f u v w u v {\rm minimize} \sum f_{uv}w_{uv} minimize∑fuvwuv。通常来讲,如果 b u > 0 b_u>0 bu>0则从源点 S S S补流过来,否则流向汇点 T T T,因此这是一个最小费用最大流问题(假设原图没有负环)。

经过 一番操作 ,可以发现对偶过后长这样:

m i n i m i z e ∑ u b u p u + ∑ u v c u v max ⁡ ( 0 , p v − p u − w u v ) {\rm minimize}\sum_{u}b_up_u+\sum_{uv}c_{uv}\max(0,p_v-p_u-w_{uv}) minimizeu∑bupu+uv∑cuvmax(0,pv−pu−wuv)

注意,两者对应的答案互为相反数,因此提取答案时要取负。

通常来讲,后面的式子一般出现在含差分约束的式子中。而为了减少一条约束中变量的数量,我们通常需要转化为前缀和。

PS:建议 b u b_u bu不要像这篇博客一样写成流入-流出,否则后面推出来怪怪的。。。

Part 2

首先根据贪心,一定是将一个商店里的 A A A个珠宝按重量从小到大排序,然后将每个商店中对应第 i i i大的珠宝放在一起,如果这样都不行的话就不合法。

进一步的,考虑设 x i , j x_{i,j} xi,j表示第 i i i个商店选的编号 ≤ j \le j ≤j的珠宝的数量(注意一定要把 i = 0 i=0 i=0的变量也建出来),那么对于一条限制 ( u , v , w ) (u,v,w) (u,v,w), ∀ i ∈ [ 1 , K v ] \forall i\in [1,K_v] ∀i∈[1,Kv],设 j j j是最小的满足 S u , j + w ≥ S v , i S_{u,j}+w\ge S_{v,i} Su,j+w≥Sv,i的数,则 x u , j − 1 ≤ x v , i − 1 x_{u,j-1}\le x_{v,i-1} xu,j−1≤xv,i−1。

现在写出问题对应的线性规划形式:

m i n i m i z e ∑ x i , j ( P i , j − P i , j + 1 ) s . t . x i , j − x i , j + 1 ≤ 0 x i , j + 1 − x i , j − C i , j + 1 ≤ 0 x u , k − x v , j ≤ 0 x i , K i − x i , 0 − A ≤ 0 x i , 0 − x i , K i + A ≤ 0 x i , 0 = x j , 0 x i , j ≥ 0 {\rm minimize} \sum x_{i,j}(P_{i,j}-P_{i,j+1})\\ s.t.\ \ \ x_{i,j}-x_{i,j+1}\le 0\\x_{i,j+1}-x_{i,j}-C_{i,j+1}\le 0\\ x_{u,k}-x_{v,j}\le 0\\ x_{i,K_i}-x_{i,0}-A\le 0\\ x_{i,0}-x_{i,K_i}+A\le 0\\ x_{i,0}=x_{j,0}\\ x_{i,j}\ge 0 minimize∑xi,j(Pi,j−Pi,j+1)s.t. xi,j−xi,j+1≤0xi,j+1−xi,j−Ci,j+1≤0xu,k−xv,j≤0xi,Ki−xi,0−A≤0xi,0−xi,Ki+A≤0xi,0=xj,0xi,j≥0

现在对于每条差分约束 x − y ≤ w x-y\le w x−y≤w,都连一条 ( y , x , ∞ , w ) (y,x,\infty,w) (y,x,∞,w)的边,然后根据 P i , j − P i , j + 1 P_{i,j}-P_{i,j+1} Pi,j−Pi,j+1的正负建立源汇点平衡流量,答案就是对应的最小费用最大流。

但是这样无法处理多组询问 。注意到原图上存在 x i , j x_{i,j} xi,j到 x i , j − 1 x_{i,j-1} xi,j−1的容量为 ∞ \infty ∞,费用为 0 0 0的边,而 x i , j x_{i,j} xi,j的流出量为 P i , j − P i , j + 1 P_{i,j}-P_{i,j+1} Pi,j−Pi,j+1,因此考虑先在 x i , j x_{i,j} xi,j到 x i , j − 1 x_{i,j-1} xi,j−1这条边流大小为 P i , j P_{i,j} Pi,j的流量,然后连一条 ( x i , j − 1 , x i , j , P i , j , 0 ) (x_{i,j-1},x_{i,j},P_{i,j},0) (xi,j−1,xi,j,Pi,j,0)的边表示可以反悔,不难发现这样每个点都自动流量平衡了 。这样我们将问题转化为了最小费用可行流 ,即不断找负环增广,直到代价 > 0 >0 >0。

现在,注意到整张图实际上只有 x i , K i x_{i,K_i} xi,Ki到 x i , 0 x_{i,0} xi,0这一条负边,因此这条边一定会被增广,这实际上就是从 x 0 , j x_{0,j} x0,j到 x i , K i x_{i,K_i} xi,Ki的最小回归代价减去 A A A(注意 j j j是任意的,这是 x i , 0 = x j , 0 x_{i,0}=x_{j,0} xi,0=xj,0建出来的边),所以如果将所有 x 0 , j x_{0,j} x0,j看成源点 S S S, x i , K i x_{i,K_i} xi,Ki看成汇点 T T T,答案就是从 S S S到 T T T不断增广,直到代价 > A >A >A时的答案。

注意到代价为 A A A的边其实是废边,所以这张图实际上和 A A A没关系了。又因为费用流具有凸性,因此二分即可。

怎么判无解?有解等价于差分约束有解,也就是说没有负环(这里指容量为 ∞ \infty ∞的边),而有负环的时候总流量一定为 ∞ \infty ∞。

大力冲费用流即可。大概要跑 ∑ P i , j \sum P_{i,j} ∑Pi,j次最短路。

cpp 复制代码
#include<bits/stdc++.h>
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
#define int ll
#define ll long long
using namespace std;
const int N=114514;
const int M=114514;
int n,m,s,t,idx; 
int head[M],nxt[M],to[M],cap[M],cost[M],tot=1;
queue<int>Q;
int dis[N],pre[N],vis[N];
void add(int u,int v,int w,int c){
    tot++,to[tot]=v,nxt[tot]=head[u],head[u]=tot,cap[tot]=w,cost[tot]=c;
    tot++,to[tot]=u,nxt[tot]=head[v],head[v]=tot,cap[tot]=0,cost[tot]=-c;
}
bool spfa(){
    for(int i=1;i<=idx;i++)dis[i]=inf,vis[i]=0;
    dis[s]=0;
    Q.push(s);
    vis[s]=1;
    while(Q.size()) {
        int u=Q.front();Q.pop();
        vis[u]=0;
        for(int k=head[u];k;k=nxt[k]){
            int v=to[k];
            if(cap[k]>0&&dis[u]+cost[k]<dis[v]) {
                dis[v]=dis[u]+cost[k];
                pre[v]=k;
                if(!vis[v]) {
                    vis[v]=1;
                    Q.push(v);
                }
            }
        }
    }
    return dis[t]!=inf;
}
int cnt;
ll f[N],sm1[N],sm2[N];
void EK(){
    while(spfa()){
        int now=t,flo=inf;
        while(now!=s){
            int k=pre[now];
            flo=min(flo,cap[k]);
            now=to[k^1];
        }
        f[++cnt]=dis[t],sm1[cnt]=sm1[cnt-1]+flo,sm2[cnt]=sm2[cnt-1]+flo*dis[t];
        if(sm1[cnt]>inf)break;
        now=t;
        while(now!=s){
            int k=pre[now];
            cap[k]-=flo;
            cap[k^1]+=flo;
            now=to[k^1];
        }
    }
}
int sz[35],id[35][35];
struct node{
    int s,p,c;
    bool operator <(const node &r)const{
        return s<r.s;
    }
}a[35][35];
signed main(){
    //freopen("data.in","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n,s=++idx,t=++idx;
    for(int i=1;i<=n;i++){
        cin>>sz[i];
        for(int j=1;j<=sz[i];j++)cin>>a[i][j].s>>a[i][j].p>>a[i][j].c;
        sort(a[i]+1,a[i]+1+sz[i]);
        for(int j=1;j<sz[i];j++)id[i][j]=++idx;
        id[i][0]=s,id[i][sz[i]]=t;
        for(int j=0;j<sz[i];j++){
            add(id[i][j],id[i][j+1],inf,a[i][j+1].c);
            add(id[i][j],id[i][j+1],a[i][j+1].p,0);
        }
    }
    cin>>m;
    for(int i=1;i<=m;i++){
        int u,v,w;cin>>u>>v>>w;
        int k=1;
        for(int j=1;j<=sz[v];j++){
            while(k<=sz[u]&&a[v][j].s>a[u][k].s+w)k++;
            add(id[v][j-1],id[u][k-1],inf,0);
        }
    }
    EK();
    int _Q;
    cin>>_Q;
    while(_Q--){
        int x;cin>>x;
        if(x>f[cnt]){
            cout<<-1<<"\n";
        }
        else{
            int p=lower_bound(f+1,f+1+cnt,x)-f-1;
            cout<<x*sm1[p]-sm2[p]<<"\n";
        }
    }
}
相关推荐
我们的五年7 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
一棵开花的树,枝芽无限靠近你30 分钟前
【PPTist】添加PPT模版
前端·学习·编辑器·html
VertexGeek1 小时前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
二进制_博客1 小时前
Flink学习连载文章4-flink中的各种转换操作
大数据·学习·flink
codebolt2 小时前
ADS学习记录
学习
Komorebi.py3 小时前
【Linux】-学习笔记05
linux·笔记·学习
亦枫Leonlew3 小时前
微积分复习笔记 Calculus Volume 1 - 6.5 Physical Applications
笔记·数学·微积分
冰帝海岸8 小时前
01-spring security认证笔记
java·笔记·spring
小二·9 小时前
java基础面试题笔记(基础篇)
java·笔记·python
朝九晚五ฺ10 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习