[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;
}

浙公网安备 33010602011771号