[UOI 2019 II Stage] 土豆 题解

[UOI 2019 II Stage] 土豆

给定一个 \(n\) 个点的有权有向图,每个点有 \(p_i\) 个土豆要转移,有 \(k\) 个关键点,每个点可以储存 \(c_j\) 个土豆,走过一条边要花费边的边权的时间,询问将所有土豆都储存到关键点的最短用时,若不可能全部存储则输出 \(-1\)。\(n \le 10^5,k \le 18,p_i,c_i,w \le 10^9\)。

发现这个存储土豆是一个二分图的匹配过程,只不过是有容量和供量的。因为用时具有单调性不妨二分答案,当前限时为 \(mid\) 的情况下,如何确定一个点能到达哪些关键点呢?

观察到 \(k \le 18\),所以我们可以建反图,以每个关键点为起点跑一次最短路,只要 \(dis_{i,j} \le mid\),\(j\) 就可以向 \(i\) 连边,边权为 \(inf\),所有点再向源点连边,边权为 \(p_i\),关键点再向汇点连边,边权为 \(c_j\),这时跑最大流,若最大流大于了总的土豆数,这个时限下就可以。

时间复杂度 \(O(kn\log n+m\sqrt n\log n)\),可以得到 63 分。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m,k,s,t;
const int N = 1e5+10,inf=1e18;
struct edge{
    int v,w;
};
struct node{
    int s,v,t;
};
vector<edge>g[N];
vector<int>d[N];
int p[N],c[N],dis[19][N],id[N],vis[N];
void dijkstra(int s,int *dis){
    for(int i=1;i<=n;i++){dis[i]=inf,vis[i]=0;}
    dis[s]=0;
    pq<pii,vector<pii>,greater<pii>>q;
    q.push({0,s});
    while(!q.empty()){
        int u=q.top().second;q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(auto [v,w]:g[u]){
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push({dis[v],v});
            }
        }
    }
}
struct flow{
    int head[N],cnt=1,nxt[20*N],to[20*N],c[20*N],cur[N],dep[N];
    void add(int u,int v,int w){
        nxt[++cnt]=head[u],head[u]=cnt;
        to[cnt]=v,c[cnt]=w;
        nxt[++cnt]=head[v],head[v]=cnt;
        to[cnt]=u,c[cnt]=0;
    }
    bool bfs(){
        for(int i=0;i<=t;i++)dep[i]=0;
        queue<int>q;q.push(s);
        dep[s]=1;
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=head[u];i;i=nxt[i]){
                int v=to[i];
                if(!c[i]||dep[v])continue;
                q.push(v);
                dep[v]=dep[u]+1;
                if(v==t)return 1;
            }
        }
        return 0;
    }
    int dfs(int u,int mf){
        int sum=0;
        if(u==t)return mf;
        for(int i=cur[u];i;i=nxt[i]){
            cur[u]=i;
            int v=to[i];
            if(!c[i]||dep[v]!=dep[u]+1)continue;
            int f=dfs(v,min(mf,c[i]));
            c[i]-=f;c[i^1]+=f;
            sum+=f;mf-=f;
            if(!mf)break;
        }
        if(!sum)dep[u]=0;
        return sum;
    }
    int dinic(){
        int ans=0;
        while(bfs()){
            for(int i=0;i<=t;i++)cur[i]=head[i];
            ans+=dfs(s,inf);
        }
        return ans;
    }
    void clear(){
        for(int i=0;i<=t;i++)head[i]=0;
        for(int i=2;i<=cnt;i++){
            nxt[i]=to[i]=c[i]=0;
        }
        cnt=1;
    }
}f;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>k;
    int sum=0;
    for(int i=1;i<=n;i++){
        cin>>p[i];
        sum+=p[i];
    }
    int l=1,r=1e15,ans=-1;
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        g[v].push_back({u,w});
    }
    for(int i=1;i<=k;i++){
        int x;cin>>x;id[i]=x;
        cin>>c[x];
    }
    
    s=0,t=n+k+1;
    for(int i=1;i<=k;i++){
        dijkstra(id[i],dis[i]);
    }
    while(l<=r){
        int mid=(l+r)/2;
        f.clear();
        for(int i=1;i<=k;i++){
            for(int j=1;j<=n;j++){
                if(dis[i][j]<=mid&&p[j]){
                    f.add(j,i+n,inf);
                }
            }
            f.add(i+n,t,c[id[i]]);
        }
        
        for(int i=1;i<=n;i++){
            if(p[i]){
                f.add(s,i,p[i]);
            }
        }
        if(f.dinic()>=sum){
            ans=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    cout<<ans;
    return 0;
}

考虑优化,看见 \(k \le 18\),还能想到 \(2^k\) 的点集。与点集和二分图匹配联系起来的话,要想到 Hall 定理。带权 Hal 定理的内容为,设关键点集为 \(Y\),有完美匹配的充要条件为,\(\forall S \subseteq Y,\sum_{i \in M(S)} p_i \le \sum_{i \in S} c_i\),其中 \(M(S)\) 表示所有只能到达 \(S\) 之中的点的集合。

我们并不可能把每个 \(M(S)\) 都表示出来,毕竟 \(M(S)\) 的量级是 \(2^n\) 而不是 \(2^k\)。那就考虑 \(arv_i\) 表示点 \(i\) 在当前时限下能到达的关键点集,给 \(h(arv_i)\) 加上 \(p_i\),但是所有子集包含 \(arv_i\) 的集合都应该加上 \(p_i\),所以我们就高维前缀和求子集和就行了。令 \(f(S)\) 表示关键点集的容量,只要保证 \(\forall S \subseteq Y,f(S) \ge H(S)\) 就说明当前符合条件。

时间复杂度 \(O(kn\log n+k2^k\log n)\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m,k,s,t;
const int N = 1e5+10,inf=1e18;
struct edge{
    int v,w;
};
struct node{
    int s,v,t;
};
vector<edge>g[N];
vector<int>d[N];
int p[N],c[N],dis[19][N],id[N],vis[N];
void dijkstra(int s,int *dis){
    for(int i=1;i<=n;i++){dis[i]=inf,vis[i]=0;}
    dis[s]=0;
    pq<pii,vector<pii>,greater<pii>>q;
    q.push({0,s});
    while(!q.empty()){
        int u=q.top().second;q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(auto [v,w]:g[u]){
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push({dis[v],v});
            }
        }
    }
}
int arv[N],f[1<<19],h[1<<19];
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>k;
    int sum=0;
    for(int i=1;i<=n;i++){
        cin>>p[i];
        sum+=p[i];
    }
    int l=0,r=1e15,ans=-1;
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        g[v].push_back({u,w});
    }
    for(int i=1;i<=k;i++){
        cin>>id[i];
        cin>>c[i];
    }
    for(int i=1;i<=k;i++){
        dijkstra(id[i],dis[i]);
    }
    for(int i=0;i<k;i++){
        f[1<<i]+=c[i+1];
    }
    for(int i=0;i<k;i++){
        for(int s=0;s<(1<<k);s++){
            if((s>>i)&1)f[s]+=f[s^(1<<i)];
        }
    }
    while(l<=r){
        int mid=(l+r)/2;
        for(int i=1;i<=n;i++)arv[i]=0;
        for(int i=1;i<=k;i++){
            for(int j=1;j<=n;j++){
                if(dis[i][j]<=mid){
                    arv[j]|=(1<<(i-1));
                }
            }
        }
        for(int s=0;s<(1<<k);s++)h[s]=0;
        for(int j=1;j<=n;j++){
            h[arv[j]]+=p[j];
        }
        for(int i=0;i<k;i++){
            for(int s=0;s<(1<<k);s++){
                if((s>>i)&1)h[s]+=h[s^(1<<i)];
            }
        }
        int flag=1;
        for(int s=0;s<(1<<k);s++){
            flag&=(f[s]>=h[s]);
        }
        if(flag){
            ans=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    cout<<ans;
    return 0;
}
posted @ 2026-10-04 21:09  farenara_sayowell  阅读(7)  评论(0)    收藏  举报