P9902 『PG2』模拟最大流 题解
Sol
模拟最大流的一般套路就是求最小割。
题目保证了 \(u<v\),所以我们可以得到如下暴力:
设 \(f_{i,S}\) 表示前 \(i\) 个点,从 \(1\) 能到集合 \(S\) 中的点,割掉的最小边权。那么转移有:
\[f_{i,S} \to f_{i+1,S \cup \{i+1\}}
\]
\[f_{i,S} + \sum_{j \in S}w_{j,i+1} \to f_{i+1,S}
\]
其中 \(w_{i,j}\) 代表 \(u=i,v=j\) 的总容量,如果没有边即为 \(0\),应该好理解吧。
这样是 \(O(n^2 2^n)\) 的,需要优化。
我们发现我们还有一个最重要的性质没用,\(v-u \le k\),而且 \(k\) 很小。
此时你发现我们上面第二种转移枚举的 \(j\) 如果满足 \(j < i-k+1\),那么 \(w_{j,i+1}\) 一定为 \(0\)。
那么我们就不用维护 \(1\) 是否能到编号小于 \(i-k+1\) 的点,此时我们的 \(S\) 只维护了 \([i-k+1,i]\) 内的点,那么时间复杂度就降为了 \(O(n k 2^k)\),可以通过此题。
Code
#include<bits/extc++.h>
using namespace std;
#define int long long
const int N=8e4+5;
int n,m,k,f[1<<7],tmp[1<<7];
__gnu_pbds::gp_hash_table<int,int>w;
inline int hs(int x,int y)
{
return x*(n+1)+y;
}
inline void ckmin(int &x,int y){if(y<x)x=y;}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>k;
while(m--)
{
int x,y,val;
cin>>x>>y>>val;
w[hs(x,y)]+=val;
}
memset(f,0x3f,sizeof f);
f[1<<(k-1)]=0;
for(int j=1;j<n;j++)
{
memset(tmp,0x3f,sizeof tmp);
for(int i=0;i<(1<<k);i++)
{
if(f[i]>2e9)
continue;
ckmin(tmp[(i>>1)|(1<<(k-1))],f[i]);
int val=0,lt=j-k+1;
for(int p=0;p<k;p++)
if((i>>p)&1)
val+=w[hs(lt+p,j+1)];
ckmin(tmp[i>>1],f[i]+val);
}
memcpy(f,tmp,sizeof tmp);
}
int ans=0x3f3f3f3f3f3f3f3f;
for(int i=0;i<(1<<(k-1));i++)
ckmin(ans,f[i]);
cout<<ans<<"\n";
return 0;
}

浙公网安备 33010602011771号