首页 > 其他分享 >换根 dp

换根 dp

时间:2024-08-05 13:39:27浏览次数:12  
标签:sz int void fa dfs1 solve 换根 dp


板子
P2986 [USACO10MAR] Great Cow Gathering G
P3478 [POI2008] STA-Station


P3047 [USACO12FEB] Nearby Cows G
很好的一题
f[i][j] 表示与点 i (向下(点 i 儿子1中的点))距离为 j 的点的权值和
g[i][j] 表示与点 i (所有)距离为 j 的点的权值和
需要特别注意的是刚开始时 g[i][j] = f[i][j] (而不是 g[i][j] = 0)

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"

using i64 = long long;

const int N = 1e5 + 100;

int n, k, a[N], f[N][25], g[N][25];
vector<int> G[N];

void dfs1(int x, int fa) {
    f[x][0] = a[x];
    g[x][0] = a[x];
    for (auto y : G[x]) {
        if (y == fa) continue;
        dfs1(y, x);
        for (int i = 1; i <= k; i++) {
            f[x][i] += f[y][i - 1];
            g[x][i] += g[y][i - 1];
        }
    }
}

void dfs2(int x, int fa) {
    for (auto y : G[x]) {
        if (y == fa) continue;
        g[y][1] += a[x];
        for (int i = 2; i <= k; i++) {
            g[y][i] += g[x][i - 1] - f[y][i - 2];
        }
        dfs2(y, x);
    }
}

void solve() {
    cin >> n >> k;
    for (int i = 1; i < n; i++) {
        int x, y; cin >> x >> y;
        G[x].push_back(y);
        G[y].push_back(x);
    }

    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    dfs1(1, 1); dfs2(1, 1);

    for (int i = 1; i <= n; i++) {
        i64 res = 0;
        for (int j = 0; j <= k; j++) {
            res += g[i][j];
        }
        cout << res << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // int T = 1; cin >> T;
    // while (T--) solve();
    solve();
    return 0;
}
View Code

 

Maximum White Subtree

也是挺不错的一题
有一个小 trick
设子图中白点个数为 cnt1,黑点个数为 cnt2,请最大化 cnt1 - cnt2
把白点点权视作 1, 黑点点权视作 -1
f[i] 表示点 i 向下能得到的最大权值为多少
g[i] 表示点 i 的答案
当 f[i] < 0
则父亲 fa 一定不会选择点 i 计入答案
g[fa] > 0 g[i] = g[fa] + f[i]
g[fa] < 0 g[i] = f[i];
g[i] = max(g[fa] + f[i], f[i]);
当 f[i] > 0
则父亲 fa 一定会选择点 i 计入答案
g[i] = max(f[i], g[fa]);

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"

using i64 = long long;

const int N = 2e5 + 100;

int a[N], f[N], g[N], n;
vector<int> G[N];

void dfs1(int x, int fa) {
    f[x] = a[x];
    g[x] = a[x];
    for (auto y : G[x]) {
        if (y == fa) continue;
        dfs1(y, x);
        f[x] += max(0, f[y]);
        g[x] += max(0, g[y]);
    }
}

void dfs2(int x, int fa) {
    for (auto y : G[x]) {
        if (y == fa) continue;
        if (f[y] < 0) g[y] = max(f[y], f[y] + g[x]);
        else g[y] = max(g[x], f[y]); 
        dfs2(y, x);
    }
}

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        if (!a[i]) a[i] = -1;
    }

    for (int i = 1; i < n; i++) {
        int x, y; cin >> x >> y;
        G[x].push_back(y);
        G[y].push_back(x);
    }

    dfs1(1, 1); dfs2(1, 1);

    for (int i = 1; i <= n; i++) {
        cout << g[i] << ' ';
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // int T = 1; cin >> T;
    // while (T--) solve();
    solve();
    return 0;
}
View Code

 


 

Tree Painting

显然可以观察到的最优策略是从根往下一层一层的染点
那么问题转化为选哪个点作为根最优
换根 dp 一下
g[y] = g[x] + n - 2ll * sz[y];

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"

using i64 = long long;

const int N = 2e5 + 100;

int n, sz[N]; 
i64 f[N], g[N];
vector<int> G[N];

void dfs1(int x, int fa) {
    sz[x] = 1;
    for (auto y : G[x]) {
        if (y == fa) continue;
        dfs1(y, x);
        sz[x] += sz[y];
        g[x] += g[y];
    }
    g[x] += sz[x];
    f[x] = g[x];
}

void dfs2(int x, int fa) {
    for (auto y : G[x]) {
        if (y == fa) continue;
        g[y] = g[x] + n - 2ll * sz[y];
        dfs2(y, x); 
    }
}

void solve() {
    cin >> n;
    for (int i = 1; i < n; i++) {
        int x, y; cin >> x >> y;
        G[x].push_back(y);
        G[y].push_back(x);
    }

    dfs1(1, 1); 
    // for (int i = 1; i <= n; i++) {
    //     cout << i << ' ' << g[i] << endl;
    // }
    dfs2(1, 1);

    cout << *max_element(g + 1, g + 1 + n) << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // int T = 1; cin >> T;
    // while (T--) solve();
    solve();
    return 0;
}
View Code

 


 

Centroids

很深刻的一题
显然的一件事情是我们指定一个根后要做的事情是 : 消减当前根中子树size最大的那颗子树大小, 使他小于 n / 2
那么问题转化为我们需要维护出以当前点 x 所能得到的最大的 <= n / 2 的子树
采用换根 dp, 一个点有两种子树, 一种是本来他就有的子树, 一种是换根后出去他自己子树后他向上父亲那块子树
第一次 dfs 求出每个点向下能得到的最大的 <= n / 2 的最大的子树是多少, 同时求出每个点 sz 最大和次大的子树是哪两个
第二次求解他向上父亲的子树, 若 n - sz[x] <= n / 2 显然是选择1在外面的整颗子树
若不是, 显然我们需要去在外面的子树中寻找一个最大的 sz 去消减外面那颗子树
这时候就要用到之前维护出来的(同时求出每个点 sz 最大和次大的子树是哪两个)

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"

using i64 = long long;

const int N = 4e5 + 100;

int f[N], g[N], sz[N], n;
vector<int> G[N];

int id[N][2];
void dfs1(int x, int fa) {
    sz[x] = 1;
    for (auto y : G[x]) {
        if (y == fa) continue;
        dfs1(y, x);
        sz[x] += sz[y];
        f[x] = max(f[x], f[y]);
        if (sz[y] > sz[id[x][0]]) {
            id[x][1] = id[x][0];
            id[x][0] = y;
        } else if (sz[y] > sz[id[x][1]]) {
            id[x][1] = y;
        }
    }
    if (sz[x] <= n / 2) f[x] = sz[x];
}

void dfs2(int x, int fa) {
    for (auto y : G[x]) {
        if (y == fa) continue;
        if (n - sz[y] <= n / 2) g[y] = n - sz[y];
        else if (y != id[x][0]) g[y] = max(g[x], f[id[x][0]]);
        else g[y] = max(g[x], f[id[x][1]]);
        dfs2(y, x);
    }
}

void solve() {
    cin >> n;
    for (int i = 1; i < n; i++) {
        int x, y; cin >> x >> y;
        G[x].push_back(y);
        G[y].push_back(x);
    }

    dfs1(1, 1); dfs2(1, 1);

    for (int i = 1; i <= n; i++) {
        if (n - sz[i] > sz[id[i][0]]) cout << (n - sz[i] - g[i] <= n / 2) << ' ';
        else cout << (sz[id[i][0]] - f[id[i][0]] <= n / 2) << ' ';
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // int T = 1; cin >> T;
    // while (T--) solve();
    solve();
    return 0;
}
View Code

 

标签:sz,int,void,fa,dfs1,solve,换根,dp
From: https://www.cnblogs.com/zhujio/p/18343046

相关文章

  • 状态压缩DP
    状态压缩DP定义:‌状态压缩是一种使用二进制数来表示状态的方法,通常用于表示只有两种状态(0和1)的对象。Acwing,291蒙特里安的梦想291.蒙德里安的梦想-AcWing题库题目概览求把N×......
  • 常见CMS漏洞(WordPress、DeDeCMS、ASPCMS、PHPMyadmin、Pageadmin)
    目录一:WordPress步骤一:进入Vulhub靶场并执行以下命令开启靶场;在浏览器中访问并安装好子...步骤二:思路是修改其WP的模板写入一句话木马后门并访问其文件即可GetShel;登陆WP后点击【外观】--》【编辑】--》404.php步骤三:访问以下连接即可获取WebShel...姿势二:上传主......
  • Luogu P1777 帮助 题解 [ 紫 ] [ 线性 dp ] [ 状压 dp ]
    帮助:大毒瘤!!!调了我2h,拍了我2h,最后没调出来,重写才AC。wdnmd。思路这题主要是线性dp,而状压dp只是最后在统计答案时的一个辅助。首先定义\(dp[i][j][k]\)为考虑前\(i\)本书,已经抽出来了\(j\)本,最后没被抽出来的一本书是高度\(k\)的最小混乱度。每次放入的书与最后一位......
  • 简析传输层协议——TCP、UDP协议
    TCP/IP协议族的传输层协议TCP(TransmissionControlProtocol)传输控制协议UDP(UserDatagramProtocol)用户数据报协议TCP协议介绍:TCP是面向连接的、可靠的进程到进程通信的协议TCP提供全双工服务,即数据可在同一时间双向传输TCP报文段:TCP将若干个字节构成一个分......
  • DP
    1)数位DP数位DP:用来解决一类特定问题,这种问题比较好辨认,一般具有这几个特征:要求统计满足一定条件的数的数量(即,最终目的为计数);这些条件经过转化后可以使用「数位」的思想去理解和判断;输入会提供一个数字区间(有时也只提供上界)来作为统计的限制;上界很大(比如10^{18}),暴力枚举......
  • centos7上dpdk绑定vfio-pci失败
    记一次使用dpdk中的报错:运行dpdk/usertools/dpdk-devbind.py-bvfio-pci02:05.0来绑定设备到vfio-pci时,报出了如下错误:Error:bindfailedfor0000:02:05.0-Cannotbindtodrivervfio-pci:[Errno19]NosuchdeviceError:unbindfailedfor0000:02:05.0-Cannot......
  • Luogu P5005 中国象棋 - 摆上马 / Luogu P8756 国际象棋 题解 [ 蓝 ] [ 状压 dp ] [
    国际象棋:模板棋盘状压。摆上马:需要点思维的棋盘状压,相比上一道题加了“蹩马脚”的设定。Easy_version:国际象棋概述一下此类棋盘问题的思路:用二进制数表示出棋盘上某一行的状态。用位运算预处理出合法的单行状态,以及需要用到的一些东西。用位运算判断前一行或者前几行能否......
  • Debian系包管理工具dpkg,apt-get,apt的区别
    解密Linux包管理:apt和apt-get的区别-系统极客(sysgeek.cn)dpkg:幕后英雄dpkg是Debian包管理系统的核心,是一个底层工具,用于直接操作.deb文件。你可以把它想象成一个搬运工,负责把软件包里的「内容」搬到电脑里。但是,它不处理依赖关系,这项工作交由apt或apt-get来完成。ap......
  • wordpress站点转移
    title:wordpress站点转移date:2024/7/1311:11:11tag:linux学习categories:wordpress建设description:搭建后的优化top_img:https://cdn.jsdelivr.net/gh/xiaowang872/blogimage@main/images/QQ%E6%88%AA%E5%9B%BE20240713105724.pngcover:https://cdn.jsdelivr.net/......
  • Luogu P3959 宝藏 题解 [ 紫 ] [ 状压 dp ] [ 二项式定理 ]
    宝藏:一个对着蓝书代码调都能调两个小时的大毒瘤,但是思路还是很值得借鉴的,有普通状压和三进制状压两种做法,或者暴搜剪枝也可以(这里不介绍暴搜剪枝做法)。普通状压做法观察到\(n\le12\),首先想到状压。但考虑到普通的状压不太行,因为\(K\)这个数算在代价里,会导致这个dp有后效......