牛客/科大讯飞杯 G题血压游戏(虚树 dp)

https://ac.nowcoder.com/acm/contest/5278/G

 

题意很好理解。而且很容易发现树中同一深度的松鼠才会打架。

预处理出节点的深度和dfs序,然后枚举树的深度,同一深度的所有节点和根节点s去建虚树,每建好一次就从根节点s出发跑一次树型dp

dp的转移方程比较好想,设当前节点为x,si为x的子树,则dp[x] = ∑max(1,dp[si] + (depth[si] - depth[x]) ) (dp[si]需要>0)

depth[]表示节点在原树的深度,最终每棵虚树对答案的贡献是 max(1,dp[x] - 1 )(dp[x]>0)

复杂度O(nlogn)

 

  1 #include<bits/stdc++.h>
  2 typedef long long ll;
  3 using namespace std;
  4 const int maxbit = 20;
  5 const int maxn = 2e5+5;
  6 vector<int> G[maxn],vt[maxn],p[maxn];//vt为虚树
  7 ll a[maxn],dp[maxn];
  8 int depth[maxn],fa[maxn][maxbit],Log[maxn],in[maxn];//in数组为dfs序
  9 int n,cnt,s;
 10 void add(int u,int v){G[u].push_back(v),G[v].push_back(u);}
 11 bool cmp(int u,int v) {return in[u]<in[v];}
 12 void pre(){
 13     Log[0] = -1;
 14     Log[1] = 0,Log[2] = 1;
 15     for(int i = 3;i<maxn;i++) Log[i] = Log[i/2] + 1;
 16 }
 17 void dfs(int cur,int father){//dfs预处理
 18     in[cur] = ++cnt;//处理dfs序
 19     depth[cur] = depth[father] + 1;//当前结点的深度为父亲结点+1
 20     fa[cur][0] = father;//更新当前结点的父亲结点
 21     for(int j = 1;(1<<j)<=n;j++){//倍增更新当前结点的祖先
 22         fa[cur][j] = fa[fa[cur][j-1]][j-1];
 23     }
 24     for(int i = 0;i<G[cur].size() ;i++){
 25         if(G[cur][i] != father) {//dfs遍历
 26             dfs(G[cur][i],cur);
 27         }
 28     }
 29 }
 30 int LCA(int u,int v){
 31     if(depth[u]<depth[v]) swap(u,v);
 32     int dist = depth[u] - depth[v];//深度差
 33     while(depth[u]!=depth[v]){//把较深的结点u倍增到与v高度相等
 34         u = fa[u][Log[depth[u]-depth[v]]];
 35     }
 36     if(u == v) return u;//如果u倍增到v,说明v是u的LCA
 37     for(int i = Log[depth[u]];i>=0;i--){//否则两者同时向上倍增
 38         if(fa[u][i]!=fa[v][i]){//如果向上倍增的祖先不同,说明是可以继续倍增
 39             u = fa[u][i];//替换两个结点
 40             v = fa[v][i];
 41         }
 42     }
 43     return fa[u][0];//最终结果为u v向上一层就是LCA
 44 }
 45  
 46 void build (int indx){//传虚树和深度为indx的数组
 47     stack<int> st;
 48     st.push(s);//入栈s节点
 49     vt[s].clear();
 50     int tmp ;
 51     for(int i = 0;i<p[indx].size();i++){
 52         tmp = 0;
 53         int cur = LCA(p[indx][i],st.top());//求出栈顶元素和p[i]的LCA
 54         while(!st.empty() && LCA(cur,st.top()) != st.top()){//如果LCA和栈顶元素不一样,建栈中虚树的边
 55             if(tmp) vt[st.top()].push_back(tmp);
 56             tmp = st.top();
 57             st.pop();
 58         }
 59         if(st.empty() || st.top()!=cur){
 60             st.push(cur);//如果栈为空或者栈顶元素不等于LCA,入栈
 61             vt[cur].clear();
 62         }
 63         if(tmp) vt[st.top()].push_back(tmp);//把最后的tmp节点加入链中
 64         st.push(p[indx][i]);//当前p[i]入栈
 65         vt[p[indx][i]].clear();
 66     }
 67     tmp = 0;
 68     while(!st.empty()){//加入最后一条链
 69         if(tmp) vt[st.top()].push_back(tmp);
 70         tmp = st.top();
 71         st.pop();
 72     }
 73 }
 74 void getdp(int cur){//传需要dp的子虚树
 75     dp[cur] = 0;
 76     if(vt[cur].size() == 0) {
 77         dp[cur] = a[cur];
 78         return;
 79     }
 80     for(int i = 0;i<vt[cur].size();i++){
 81         int v = vt[cur][i];
 82         getdp(v);
 83         if(dp[v]!=0){
 84             dp[cur]+=max((ll)1,dp[v]-(depth[v]-depth[cur]));
 85         }
 86     }
 87 }
 88 int main(){
 89     scanf("%d%d",&n,&s);
 90     for(int i = 1;i<=n;i++){
 91         scanf("%lld",&a[i]);
 92     }
 93     for (int i = 0; i < n-1; ++i)
 94     {
 95         int u,v;
 96         scanf("%d%d",&u,&v);
 97         add(u,v);
 98         /* code */
 99     }
100     pre();
101     dfs(s,0);
102     ll ans = 0;
103     if(a[s]>1) ans+=(a[s]-1);
104     else ans+=a[s];
105     for(int i = 1;i<=n;i++) p[depth[i]].push_back(i);
106     for(int i = 2;i<=n;i++){
107         sort(p[i].begin(),p[i].end(),cmp);//深度一样的节点按dfs序排序
108         if(p[i].size() == 0) continue;
109         build(i);//同一深度的节点建虚树
110         getdp(s);
111         if(dp[s]>1) ans+=dp[s]-1;
112         else ans+=dp[s];
113         vt[s].clear();
114         for(int j = 0;j<p[i].size();j++) vt[p[i][j]].clear();
115     }
116     printf("%lld",ans);
117     return 0;
118 }

 

上一篇:【BZOJ4539】[HNOI2016] 树(线段树合并+倍增LCA)


下一篇:linux开学第二课之