P2495 [SDOI2011]消耗战 lca倍增+虚树+树形dp

题目:给出n个点的树  q次询问  问切断 k个点(不和1号点联通)的最小代价是多少

思路:树形dp  sum[i]表示切断i的子树中需要切断的点的最小代价是多少 mi[i]表示1--i中的最小边权

sum[i]=min(mi[i],sigma(min(mi[v],sum[v]) (v∈i.son))

从根向上dp  这里巧妙运用了欧拉序(每个点入和出的按时间顺序排列的序列)

题目链接:https://www.luogu.org/problemnew/show/P2495

参考:https://www.luogu.org/problemnew/solution/P2495

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 const int maxn = 250007;
  4 typedef long long ll;
  5 const int N=maxn;
  6 struct data{
  7     int v;int nxt;ll val;
  8 }edge[2*maxn];
  9 int dfu;
 10 int dfin[N];//欧拉序入的时间戳
 11 int dfou[N];//欧拉序列出的时间戳
 12 int fa[22][N];//倍增用
 13 ll mi[N];//i到1号点路径中最小的边权
 14 int dep[N]; //深度
 15 int lca(int u,int v){//倍增lca
 16     if(dep[u]<dep[v])swap(u,v);
 17     int del=dep[u]-dep[v];
 18     for(int i=0;del;del>>=1,i++){
 19         if(del&1){
 20             u=fa[i][u];
 21         }
 22     }//到同一深度
 23     if(u==v)return u;
 24     for(int i=20;i>=0;i--){
 25         if(fa[i][u]!=fa[i][v]){u=fa[i][u],v=fa[i][v];}
 26     } //从到lca的距离的二进制理解 即可立即为什么fa[0][v]就是是lca
 27     return fa[0][v];
 28 }
 29 int alist[maxn],cnt;int n;
 30 
 31 void dfs(int x){
 32     dfin[x]=++dfu;//首位是入 出 时间戳
 33     for(int i=1;fa[i-1][x];i++){//更新父节点信息
 34         fa[i][x]=fa[i-1][fa[i-1][x]];
 35     }
 36     int nxt=alist[x];
 37     while(nxt){//更新最小边权
 38         int v=edge[nxt].v,val=edge[nxt].val;
 39         if(dfin[v]==0){
 40             dep[v]=dep[x]+1;
 41             mi[v]=min(mi[x],1ll*val);
 42             fa[0][v]=x;
 43             dfs(v);
 44         }
 45         nxt=edge[nxt].nxt;
 46     }
 47     dfou[x]=++dfu;
 48     return ;
 49 }
 50 inline void add(int u,int v,ll val)
 51 {    edge[++cnt].v=v;
 52     edge[cnt].nxt=alist[u];
 53     alist[u]=cnt;
 54     edge[cnt].val=val;
 55 }
 56 bool cmp(int x,int y){//分为正负即可方便得判断是入 还是出
 57     int k1=(x>0)?dfin[x]:dfou[-x];
 58     int k2=(y>0)?dfin[y]:dfou[-y];
 59     return k1<k2;
 60 }
 61 int tr[4*N];
 62 stack<int>s;
 63 int m;
 64 bool book[N];
 65 ll sum[N];
 66 
 67 int main(){
 68     scanf("%d",&n);
 69     for(int i=1;i<=n-1;i++){
 70         int x,y,z;
 71         scanf("%d%d%d",&x,&y,&z);
 72         add(x,y,z);add(y,x,z);
 73     }
 74     mi[1]=0x3f3f3f3f;
 75     dfs(1);//建立欧拉序
 76     scanf("%d",&m);
 77     for(int i=1;i<=m;i++){
 78         int tmp;
 79         scanf("%d",&tmp);
 80         for(int j=1;j<=tmp;j++){//把要求的点标记
 81             scanf("%d",&tr[j]);
 82             book[tr[j]]=1;
 83             sum[tr[j]]=mi[tr[j]];//初始化删除重要点的sum[i](sum指的是切断[i]的子树中所有重要点的最小代价 初始化为切掉该点即 从1--i的最小边权 意思是直接把这个子树切掉了)
 84         }
 85         sort(tr+1,tr+tmp+1,cmp);//对欧拉序列排序建立虚树
 86         for(int j=1;j<tmp;j++){
 87             int lc=lca(tr[j],tr[j+1]);
 88             if(!book[lc]){//树的建立
 89                 tr[++tmp]=lc;
 90                 book[lc]=1;
 91             }
 92         }
 93         int nc=tmp;
 94         for(int j=1;j<=nc;j++){//把每个点的负时间戳也加入 用负数表示这个的点现在是出去的点
 95             tr[++tmp]=-tr[j];
 96         }
 97         if(!book[1]){//强行把1号点加进来当根
 98             tr[++tmp]=1;tr[++tmp]=-1;
 99         }
100         sort(tr+1,tr+tmp+1,cmp);//重构欧拉序
101         for(int j=1;j<=tmp;j++){//模拟dfs 因为1--tmp是欧拉序列 所以可以直接用
102             if(tr[j]>0)s.push(tr[j]);//进栈
103             else {
104                 int now=s.top();s.pop();
105                 if(now!=1){
106             //状态转移方程sum[i]=sigma(min(mi[v],sum[v]) (v∈i.son)
107                     int fa=s.top();
108                     sum[fa]+=min(sum[now],mi[now]);
109                 }    
110                 else printf("%lld
",sum[1]);
111                 sum[now]=0;
112                 book[now]=false;
113             }
114         }
115     }
116 
117     return 0;
118 }
View Code
原文地址:https://www.cnblogs.com/ttttttttrx/p/10632774.html