Luogu P3931 SAC E#1 - 一道难题 Tree 题解
闲扯
为了学习最小割到了这道题,但是看题解的时候发现做法还有树形 \(DP\) 毕竟没怎么认真想过题,写完最小割之后,顺手来了发树形 \(DP\) ,然后就过了,感觉真爽啊~~
题面
Solution
解法一:最小割最大流
因为要求是所有的叶节点都不能到达根结点,而且可以删除边,要求删边的最小代价,看着就很像最小割。但是仔细一看,发现割完之后叶节点组成了很多个点集,怎么办?
我们只需要再虚拟一个超级汇点,将所有的根结点全部连向该节点,且边的流量均为 \(INF\) 。(因为这些边是我们人为加的,肯定不能选这些边割去)
这时候我们可以看出,当所有的边割完之后,实际上是将这幅图变成了两个不相交的点集,且 \(S\) 和 \(T\) 分别处于一侧。这样就变成了最小割的模板了,直接跑最大流即可(最小割在数值上等于最大流,不清楚的自行百度)。
解法二:树形 \(DP\)
因为叶节点不能到根结点,我们考虑树形 \(DP\) 从下向上统计。这是什么逻辑(雾
考虑 \(dp_u\) 表示以 \(u\) 为根节点时,所需要的最小代价,转移方程为: \(dp_u=\sum \min(dp_i,dis_i)\) 。其中 \(i\) 为 \(u\) 的子节点, \(dis\) 表示 \(i\) 到 \(u\) 的距离。
理解:对于包含于以 \(i\) 为根结点的子树的叶节点,割去他们所需的最小代价为直接割去这颗子树和保持原来做法的最小值,而一个 \(u\) 可能有多个子树,所以要求和(不同子树相互独立,互不影响)。
需要注意的时候,当 \(u\) 的子节点 \(i\) 就为叶节点时, \(dp_i\) 要设为 \(INF\) ,这样可以保证删去该点的值一定为 \(dis_i\) 。
Code
解法一:
#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
int f=1;char k=getchar();x=0;
for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
x*=f;
}
template<class T>il print(T x){
if(x/10) print(x/10);
putchar(x%10+'0');
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
int res=1,bas=x%mod;
while(m){
if(m&1) res=(res*bas)%mod;
bas=(bas*bas)%mod,m>>=1;
}
return res%mod;
}
const int MAXN = 1e5+5;
int n,s,t,u,v,d,cur[MAXN],head[MAXN],num_edge=-1,dis[MAXN];
struct Edge{
int next,to,w;
Edge(){}
Edge(int next,int to,int w):next(next),to(to),w(w){}
}edge[MAXN<<2];
il add_edge(int u,int v,int w){
edge[++num_edge]=Edge(head[u],v,w),head[u]=num_edge;
edge[++num_edge]=Edge(head[v],u,w),head[v]=num_edge;
}
il build(int u,int fa){
bool fla=0;
for(ri i=head[u];i!=-1;i=edge[i].next){
if(edge[i].to==fa) continue;
edge[i^1].w=0;
build(edge[i].to,u),fla=1;
}
if(!fla) add_edge(u,t,INF);
}
inl bool BFS(int s,int t){
del(dis,0),dis[s]=1;
queue<int> q;q.push(s);
while(!q.empty()){
ri pos=q.front();q.pop();
for(ri i=head[pos];i!=-1;i=edge[i].next)
if(!dis[edge[i].to]&&edge[i].w>0){
dis[edge[i].to]=dis[pos]+1;
if(edge[i].to==t) return true;
q.push(edge[i].to);
}
}
return false;
}
it DFS(int now,int t,int flow){
if(now==t) return flow;
ri s=0,k;
for(ri &i=cur[now];i!=-1;i=edge[i].next)
if(dis[edge[i].to]==dis[now]+1&&edge[i].w>0){
k=DFS(edge[i].to,t,min(flow-s,edge[i].w));
s+=k,edge[i].w-=k,edge[i^1].w+=k;
if(s==flow) break;
}
if(s==0) dis[now]=0;
return s;
}
it Dinic(int s,int t){
ri res=0;
while(BFS(s,t)){
memcpy(cur,head,sizeof(head));
res+=DFS(s,t,INF);
}
return res;
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(s),t=n+1,del(head,-1);
for(ri i=1;i<n;++i) read(u),read(v),read(d),add_edge(u,v,d);
build(s,0);
printf("%d",Dinic(s,t));
return 0;
}
解法二:
#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
int f=1;char k=getchar();x=0;
for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
x*=f;
}
template<class T>il print(T x){
if(x/10) print(x/10);
putchar(x%10+'0');
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
int res=1,bas=x%mod;
while(m){
if(m&1) res=(res*bas)%mod;
bas=(bas*bas)%mod,m>>=1;
}
return res%mod;
}
const int MAXN = 1e5+5;
int n,s,dp[MAXN],u,v,d,head[MAXN],num_edge;
struct Edge{
int next,to,dis;
Edge(){}
Edge(int next,int to,int dis):next(next),to(to),dis(dis){}
}edge[MAXN<<1];
il add_edge(int u,int v,int dis){
edge[++num_edge]=Edge(head[u],v,dis),head[u]=num_edge;
edge[++num_edge]=Edge(head[v],u,dis),head[v]=num_edge;
}
il DFS(int u,int fa){
bool fla=0;
for(ri i=head[u];i;i=edge[i].next){
if(edge[i].to==fa) continue;fla=1;
DFS(edge[i].to,u),dp[u]+=min(dp[edge[i].to],edge[i].dis);
}
if(!fla) dp[u]=INF;
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(s);
for(ri i=1;i<n;++i) read(u),read(v),read(d),add_edge(u,v,d);
DFS(s,0);
printf("%d",dp[s]);
return 0;
}
总结
最小割也是网络流的一个重要分支,也是一个建图游戏,多做题,找到感觉,学习不同的技巧,才能够自己熟练掌握。