导入
在普通树形DPDPDP中,一般就使用一个DPDPDP数组,代表以i为根的子树中的最值,但是对于某些题目中一个数组已经不够用,此时该加入什么,以及转移方程是什么,用一个题目来说明。
题目:P3478
给定一个 nnn 个点的树,请求出一个结点,使得以这个结点为根时,所有结点的深度之和最大。
按照普通 dpdpdp 来考虑,dpdpdp 数组状态为以i为根的子树中的深度之和,用 sumsumsum 数组记录i以i为根的子树中的节点数,如果现在在 xxx,yyy 是 xxx 的孩子,那么转移就是。
dpx=Σdpy+sumy dp_x = \Sigma {dp_y+sum_y} dpx=Σdpy+sumy
即(y中)每个点都会往后再走一步走到x,所以要加sumysum_ysumy。
可是这样就结束了吗?并没有。
因为当i变为整个树的根时,它的孩子还是它的孩子,但是它的父亲也变成孩子了。
所以还要用一个数组( dp2dp2dp2 ),储存除了以 iii 为根的子树的的深度之和。
那么转移就是它父亲向上的+它父亲向下的-它那支对父亲的影响+此点到父亲那些。
如图:

转移:
dp2x=dp2fa+dpfa−(dpx+sumx)+(sum[1]−sum[x]) dp2_x = dp2_{fa} + dp_{fa} - (dp_x+sum_x) + (sum[1]-sum[x]) dp2x=dp2fa+dpfa−(dpx+sumx)+(sum[1]−sum[x])
然后这边给出代码:
//P2986来的
#include <bits/stdc++.h>
using namespace std;
#define int long long
int sum[1000005];//i的子树中一共有几头牛(包括i)
int down[1000005];//i的子树中那些牛来到这不方便值
int up[1000005];//除了i的子树中那些,其他点来这的不方便值
int n;
vector <int> a[1000005];
int cnt[1000005];
void dfs(int x,int f){
sum[x] = 1;
for (int i = 0; i < a[x].size(); i++){
int y = a[x][i];
if (y != f){
dfs(y,x);
sum[x] += sum[y];
down[x] += down[y]+sum[y];
}
}
//cout << down[x]<<" " << x << " \n";
}
void dfs2(int x,int f){
if (f!=0){
up[x] = up[f]+down[f]-(down[x]+sum[x])+(sum[1]-sum[x]);//注意sum[1]而不是n...
//+(sum[1]-sum[x])同上
//up[f]+down[f]-(down[x]+sum[x]),除了我这条枝干到f外所有点到f的不方便程度
//注意+sum[x]!
}
for (int i = 0; i < a[x].size(); i++){
int y = a[x][i];
if (y != f) dfs2(y,x);
}
}
signed main(){
ios::sync_with_stdio(false); // 关闭 C 和 C++ 输入输出同步
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n-1; i++){
int u,v;
cin >>u >> v;
a[u].push_back(v);
a[v].push_back(u);
}
dfs(1,0);
dfs2(1,0);
int max_ = -114514,id;
for (int i = 1; i <= n; i++){
if (max_ < down[i]+up[i]){
max_ = down[i]+up[i];
id = i;
}
//cout << up[i] << " " << down[i] << "\n";
}
cout << id;
return 0;
}
题目:P9437
轻松得出转移,sumsumsum 代表节点数:
dpx=Σdpy∗10len(ax)+sumy∗ax dp_x = \Sigma dp_y*10^{len(a_x)}+sum_y*a_x dpx=Σdpy∗10len(ax)+sumy∗ax
lenlenlen 函数为这个数是几位数。
这转移的解释:
加上 yyy 的那些延升到 xxx 的路(即 dpydp_ydpy 每一项往大移一下(∗10len(ax)*10^{len(a_x)}∗10len(ax),提取公因数,从 dpydp_ydpy 每一项提出来),每一项再加入axa_xax (sumy∗axsum_y*a_xsumy∗ax))
那dp2dp2dp2怎么转移?
依旧参考这个图。

可以得出:
父亲向上的:dp2fadp2_{fa}dp2fa
它父亲向下的:dpfadp_{fa}dpfa
它那支对父亲的影响:(dpx∗10len(w[fa])+sumx∗afa)(dp_x*10^{len(w[fa])}+sum_{x}*a_{fa})(dpx∗10len(w[fa])+sumx∗afa)
其实这里直接抄 dpdpdp 的转移就行。
定义 ans=dp2fa+dpfa−(dpx∗10len(a[fa])+sumx∗afa) ans = dp2_{fa}+dp_{fa}-(dp_x*10^{len(a[fa])}+sum_{x}*a_{fa})ans=dp2fa+dpfa−(dpx∗10len(a[fa])+sumx∗afa)
那此点到父亲那些的贡献就是ans∗10len(a[x])+sum2x∗axans * 10^{len(a[x])}+sum2_x*a_xans∗10len(a[x])+sum2x∗ax
sum2sum2sum2 代表 ansansans 里有几项,这个贡献跟 dpdpdp 的转移差不多,不再过多赘述了。
sum2sum2sum2的更新:
依旧按照那个图来
sumx=sumfa+sum2fa−sumxsum_x = sum_{fa} + sum2_{fa}-sum_xsumx=sumfa+sum2fa−sumx
这边可以得出代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;//变量名终于不写boeing757了!
int dp[1000004];//以i为起点的(考虑i的树)
int dp2[1000004];//以i为结尾的(考虑非i的树)
int sum[1000004];//有几条路线(考虑i的树)
int sum2[1000004];//有几条路线(考虑非i的树)
int w[1000004];
int ten[1000004];
vector <int> a[1000004];
int len(int x){
if(x == 0) return 1;
int cnt = 0;
while(x) cnt++, x /= 10;
//peepsick说不用flag。。。
return cnt;
}
void dfs(int x,int fa){
//自己到自己
dp[x] = w[x];
sum[x] ++;//自己到自己是一条
for (int i = 0; i < a[x].size(); i++){
int y = a[x][i];
if (y != fa){
dfs(y,x);
sum[x] += sum[y];
dp[x] += dp[y]%mod*ten[len(w[x])]%mod+sum[y]%mod*w[x]%mod;//加上y的那些延升到x的路(即dp[y]每一项往大移一下(*ten[len(w[x])],提取公因数,从dp[y]每一项提出来),每一项再加入w[x](sum[y]*w[x]))
}
}
}
void dfs2(int x,int fa){
if (fa != 0){
sum2[x] = sum[fa] + sum2[fa]-sum[x];
int self = dp[x]%mod*ten[len(w[fa])]%mod+sum[x]%mod*w[fa]%mod;//减去自己那支(直接抄dp的转移)
int ans = (dp2[fa]%mod+dp[fa]%mod-self+mod) %mod;
dp2[x] = ((ans%mod * ten[len(w[x])]%mod)%mod+sum2[x]%mod*w[x]%mod)%mod;//,注意加上w[x]并*位数
}
for (int i = 0; i < a[x].size(); i++){
int y = a[x][i];
if (y != fa) dfs2(y,x);
}
}
signed main(){
ten[0] = 1;
for (int i = 1; i<= 10; i++) ten[i] = ten[i-1] * 10%mod;
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> w[i];
for (int i = 1; i <= n-1; i++){
int u;
cin >> u;
a[u].push_back(i+1);
a[i+1].push_back(u);
}
dfs(1,0);
dfs2(1,0);
int ans = 0;
for (int i = 1; i <= n; i++){
(ans += dp[i]%mod+dp2[i]%mod)%=mod;
}
cout << ans;
return 0;
}
最后给出一些换根的题目

第一题为模板。
前 333 道题较为简单。
后面考察一些思考,以及代码实现有细节(至少对我来说)。
完结!

1234

被折叠的 条评论
为什么被折叠?



