YbtOJ 大全

【例题1】树上求和

设 f [ u ] [ 0 ] f[u][0] f[u][0] 表示这个点不选, f [ u ] [ 1 ] f[u][1] f[u][1] 表示这个点选。

那么转移方程 f [ u ] [ 0 ] + = max ⁡ ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) f[u][0] += \max(f[v][0],f[v][1]) f[u][0]+=max(f[v][0],f[v][1]), f [ u ] [ 1 ] = f [ v ] [ 0 ] + a [ u ] f[u][1] = f[v][0] + a[u] f[u][1]=f[v][0]+a[u]。最后的答案就是 a n s = max ⁡ ( a n s , max ⁡ ( f [ u ] [ 0 ] , f [ u ] [ 1 ] ) ) ans = \max(ans,\max(f[u][0],f[u][1])) ans=max(ans,max(f[u][0],f[u][1]))。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 2e4+10;
int head[M],f[M][2],a[M];
int n,cnt,ans = 1;
struct edge{int to,nxt;
}e[M];
inline void add(int u,int v){e[++cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs(int u,int fa){f[u][1] = a[u],f[u][0] = 0;for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dfs(v,u);f[u][0] += max(f[v][0],f[v][1]);f[u][1] = f[v][0] + a[u];ans = max(ans,max(f[u][0],f[u][1]));}
}
signed main(){n = read();rep(i,1,n) a[i] = read();rep(i,1,n-1){int u = read(),v = read();add(v,u),add(u,v);}dfs(1,0);printf("%d\n",ans);return 0;
}

【例题2】结点覆盖

设 f [ u ] [ 0 ] f[u][0] f[u][0] 选 u u u 的父亲结点, f [ u ] [ 1 ] f[u][1] f[u][1] 表示选 u u u 自己, f [ u ] [ 2 ] f[u][2] f[u][2] 表示选 u u u 的儿子。

  • 若 u u u 被其父结点覆盖,那么子结点只能被其子结点或其本身覆盖。 f [ u ] [ 0 ] + = min ⁡ ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) f[u][0] += \min(f[v][1],f[v][2]) f[u][0]+=min(f[v][1],f[v][2])
  • 若 u u u 被其本身覆盖,那它的子结点可以被其父亲或自己或孩子覆盖。 f [ u ] [ 1 ] + = min ⁡ ( f [ v ] [ 0 ] , min ⁡ ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) ) f[u][1] += \min(f[v][0],\min(f[v][1],f[v][2])) f[u][1]+=min(f[v][0],min(f[v][1],f[v][2]))
  • 若 u u u 被其子结点覆盖。由于至少要选一个子结点,其余的子结点可以被其子结点或本身覆盖。我们用一个变量记录是否选了 u u u 的一个子结点。如果都没有选,只能选最小的 v v v 来覆盖 u u u。具体转移看代码。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 3010;
int f[M][3],a[M],head[M];
int n,cnt;
struct edge{int to,nxt;
}e[M];
inline void add(int u,int v){e[++cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs(int u,int fa){int fl = 0,mi = 1e9;f[u][0] = 0,f[u][1] = a[u],f[u][2] = 0;for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dfs(v,u);f[u][0] += min(f[v][1],f[v][2]);f[u][1] += min(f[v][0],min(f[v][1],f[v][2]));if(f[v][1] < f[v][2]) fl = 1,f[u][2] += f[v][1];else f[u][2] += f[v][2],mi = min(mi,f[v][1]-f[v][2]);}if(fl == 0) f[u][2] += mi;
}
signed main(){n = read();rep(i,1,n){int u = read();a[u] = read();int m = read();rep(i,1,m){int v = read();add(u,v),add(v,u);}}dfs(1,0);printf("%d\n",min(f[1][1],f[1][2]));return 0;
}

【例题3】最长距离

求出树的直径,然后从直径的两端分别进行一次 d f s dfs dfs 算出离每一个点的距离,最后的答案就是二者的 max ⁡ \max max。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 2e4+10;
int head[M],dis1[M],dis2[M];
int n,cnt,s,maxlen;
struct edge{int to,nxt,w;
}e[M];
inline void add(int u,int v,int w){e[++cnt].to = v;e[cnt].w = w;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs1(int u,int fa){if(dis1[u] > maxlen){maxlen = dis1[u];s = u;}for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dis1[v] = dis1[u] + e[i].w;dfs1(v,u);}
}
inline void dfs2(int u,int fa){if(dis1[u] > maxlen){maxlen = dis1[u];s = u;}for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dis1[v] = dis1[u] + e[i].w;dfs2(v,u);}
}
inline void dfs3(int u,int fa){if(dis2[u] > maxlen){maxlen = dis2[u];s = u;}for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dis2[v] = dis2[u] + e[i].w;dfs3(v,u);}
}
signed main(){while(cin >> n){memset(dis1,0,sizeof(dis1));memset(dis2,0,sizeof(dis2));memset(head,0,sizeof(head));memset(e,0,sizeof(e));cnt = 0;maxlen = 0;s = 0;rep(i,2,n){int u = read(),w = read();add(u,i,w),add(i,u,w);}dfs1(1,0);memset(dis1,0,sizeof(dis1));maxlen = 0;dfs2(s,0);maxlen = 0;dfs3(s,0);rep(i,1,n) printf("%lld\n",max(dis1[i],dis2[i]));}return 0;
}

【例题4】选课方案

树上背包。设 f [ u ] [ t ] f[u][t] f[u][t] 表示在以 u u u 为根的子树中考虑前 t t t 个结点的最大学分。

由于这道题会形成森林,所以我们将每一门没有先修课的课程向 0 0 0 连边,我们强制选 0 0 0 号结点,也就是最后的答案就是 f [ 0 ] [ m + 1 ] f[0][m+1] f[0][m+1]。

在考虑到 v v v 子树时,我们枚举一个 k k k,表示在以 v v v 为根的子树中选 k k k 门课程,那么就需要在 u u u 为根的其它子树中选 t − k t-k t−k 门课程。

转移方程 f [ u ] [ j ] = max ⁡ ( f [ u ] [ j ] , f [ v ] [ k ] + f [ u ] [ j − k ] ) f[u][j] = \max(f[u][j],f[v][k]+f[u][j-k]) f[u][j]=max(f[u][j],f[v][k]+f[u][j−k])。注意这里需要 k ≤ j k \leq j k≤j 而且我们枚举 j j j 的时候需要倒着枚举,类似于 01 01 01 背包。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 610;
int n,m;
int head[M],f[M][M];
int cnt;
struct edge{int to,nxt;
}e[M];
inline void add(int u,int v){e[++cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs(int u){for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;dfs(v);for(re int j(m+1) ; j>=1 ; --j){for(re int k(0) ; k<j ; ++k){f[u][j] = max(f[u][j],f[v][k]+f[u][j-k]);}}}
}
signed main(){n = read(),m = read();rep(i,1,n){   int u = read(),x = read();add(u,i);f[i][1] = x;}dfs(0);printf("%d\n",f[0][m+1]);return 0;
}

1. 1. 1. 路径求和

我们设 s i z [ u ] siz[u] siz[u] 表示以 u u u 为根的子树大小, s u m [ u ] sum[u] sum[u] 表示以 u u u 为根的子树的叶子结点数量。根据乘法原理,我们在枚举边的时候,设边的两个端点为 u u u 和 v v v,它对答案的贡献就是 w × s i z [ v ] × ( s u m [ 1 ] − s u m [ v ] ) + w × ( s i z [ 1 ] − s i z [ v ] ) × s u m [ v ] w \times siz[v] \times (sum[1]-sum[v]) + w \times (siz[1]-siz[v]) \times sum[v] w×siz[v]×(sum[1]−sum[v])+w×(siz[1]−siz[v])×sum[v]。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 2e5+10;
int head[M],siz[M],du[M],sum[M];
int n,m,cnt,ans;
struct edge{int to,nxt,w;
}e[M];
inline void add(int u,int v,int w){e[++cnt].to = v;e[cnt].w = w;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs1(int u,int fa){siz[u] = 1;if(du[u] == 1) sum[u] = 1;for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dfs1(v,u);siz[u] += siz[v];sum[u] += sum[v];}
}
inline void dfs2(int u,int fa){for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to,w = e[i].w;if(v == fa) continue;dfs2(v,u);ans += w * (siz[v] * (sum[1]-sum[v]));ans += w * ((siz[1]-siz[v]) * sum[v]);}
}
signed main(){n = read(),m = read();rep(i,1,m){int w = read(),u = read(),v = read();add(u,v,w),add(v,u,w);du[u]++,du[v]++;}dfs1(1,0);dfs2(1,0);printf("%lld\n",ans);return 0;
}

2. 2. 2. 树上移动

只有一个人走的话,我们需要找到离 S S S 的最长路,这些边走一遍,其余的边走两遍。

两个人走的话,两条走到底的路径拼起来正好是最长链的情况最优。我们在 d f s dfs dfs 的过程中记录最长路以及次长路即可。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 2e5+10;
int n,s,sum,cnt,ans1,ans2;
int head[M],dis1[M],dis2[M];
struct edge{int to,nxt,w;
}e[M];
inline void add(int u,int v,int w){e[++cnt].to = v;e[cnt].w = w;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs(int u,int fa){for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dfs(v,u);if(dis1[v] + e[i].w > dis1[u]){dis2[u] = dis1[u];dis1[u] = dis1[v] + e[i].w;}else if(dis1[v] + e[i].w > dis2[u]) dis2[u] = dis1[v] + e[i].w;}
}
signed main(){n = read(),s = read();rep(i,1,n-1){int u = read(),v = read(),w = read();add(u,v,w),add(v,u,w);sum += 2*w;}dfs(s,0);int maxn = 0;rep(i,1,n) maxn = max(maxn,dis1[i] + dis2[i]);ans1 = sum - dis1[s];ans2 = sum - maxn;printf("%d\n%d\n",ans1,ans2);return 0;
}

3. 3. 3. 块的计数

有一种解题的思维是正难则反。

直接算包含的不好求,那么我们可以算出一共的减去不包含的。

设 f [ u ] f[u] f[u] 表示以结点 u u u 为根的联通块总数 ( ( (包含 u u u ) ) ), g [ u ] g[u] g[u] 表示以 u u u 为根的不包含最大值的联通块总数 ( ( (同样包含 u u u ) ) )。那么以 u u u 为根的包含最大值的联通块总数就是 f [ u ] − g [ u ] f[u] - g[u] f[u]−g[u]。

对于一个结点 v v v,可以选,可以不选,所以 f [ u ] = ∏ v ⊆ s o n [ u ] ( f [ v ] + 1 ) f[u] = \prod\limits_{v\subseteq son[u]}(f[v]+1) f[u]=v⊆son[u]∏​(f[v]+1)

对于 g [ u ] g[u] g[u] 来说,如果 u u u 本身是最大值,那么 g [ u ] = 0 g[u] = 0 g[u]=0,否则跟 f [ u ] f[u] f[u] 的转移类似, g [ u ] = ∏ v ⊆ s o n [ u ] ( g [ v ] + 1 ) g[u] = \prod\limits_{v\subseteq son[u]}(g[v]+1) g[u]=v⊆son[u]∏​(g[v]+1)。

最后的答案 a n s = ∑ i = 1 n ( f [ i ] − g [ i ] ) ans = \sum_{i=1}^n(f[i]-g[i]) ans=∑i=1n​(f[i]−g[i])。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 2e5+10;
const int mod = 998244353;
int head[M],f[M],g[M],a[M];
int n,cnt,ans1,ans2,maxn = -1e17;
struct edge{int to,nxt;
}e[M];
inline void add(int u,int v){e[++cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs(int u,int fa){f[u] = 1;g[u] = (a[u] != maxn);for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dfs(v,u);f[u] = f[u]*(f[v]+1)%mod;g[u] = g[u]*(g[v]+1)%mod;}ans1 = (ans1+f[u])%mod;ans2 = (ans2+g[u])%mod;
}
signed main(){n = read();rep(i,1,n) a[i] = read(),maxn = max(maxn,a[i]);rep(i,1,n-1){int u = read(),v = read();add(u,v),add(v,u);}dfs(1,0);printf("%lld\n",(ans1-ans2+mod)%mod);return 0;
}

4. 4. 4. 树的合并

先把两棵树的直径分别求出来,去最大的直径设为 m a x l e n maxlen maxlen。我们发现,将 u u u 和 v v v 连一条边,直径只有两种情况,要么是 m a x l e n maxlen maxlen,要么是 u u u 和 v v v 在其子树中的最大长度 + 1 +1 +1。

于是我们可以预处理两棵树每一个点在其树上的最远距离,然后对于数组从大到小排序,进行二分。我们要找到 f 1 [ u ] + f 2 [ v ] + 1 ≥ m a x l e n f1[u] + f2[v] + 1 \geq maxlen f1[u]+f2[v]+1≥maxlen 的贡献就是 f 1 [ u ] + f 2 [ v ] + 1 f1[u] + f2[v] + 1 f1[u]+f2[v]+1,否则贡献就是 m a x l e n maxlen maxlen。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 4e5+10;
int n,m,cnt,maxlen,s,mx;
int head[M],dis1[M],dis2[M],f1[M],f2[M],sum[M];
struct edge{int to,nxt;
}e[M];
inline bool cmp(int x,int y) { return x > y; }
inline void add(int u,int v){e[++cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs1(int u,int fa){if(dis1[u] > maxlen){maxlen = dis1[u];s = u;}for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dis1[v] = dis1[u] + 1;dfs1(v,u);}
}
inline void dfs2(int u,int fa){if(dis1[u] > maxlen){maxlen = dis1[u];s = u;}for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dis1[v] = dis1[u] + 1;dfs2(v,u);}
}
inline void dfs3(int u,int fa){if(dis2[u] > maxlen){maxlen = dis2[u];s = u;}for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dis2[v] = dis2[u] + 1;dfs3(v,u);}
}
signed main(){n = read(),m = read();rep(i,1,n-1){int u = read(),v = read();add(u,v),add(v,u);}dfs1(1,0);memset(dis1,0,sizeof(dis1));maxlen = 0;dfs2(s,0);maxlen = 0;dfs3(s,0);mx = max(mx,maxlen);rep(i,1,n) f1[i] = max(dis1[i],dis2[i]);rep(i,1,m-1){int u = read(),v = read();add(u+n,v+n),add(v+n,u+n);}memset(dis1,0,sizeof(dis1));memset(dis2,0,sizeof(dis2));maxlen = 0;s = 0;dfs1(n+1,0);memset(dis1,0,sizeof(dis1));maxlen = 0;dfs2(s,0);maxlen = 0;dfs3(s,0);mx = max(mx,maxlen);rep(i,n+1,n+m) f2[i] = max(dis1[i],dis2[i]);sort(f1+1,f1+n+1,cmp);rep(i,1,n) sum[i] = sum[i-1] + f1[i];int ans = 0;rep(i,n+1,n+m){int l = 0,r = n,p = 0;while(l <= r){int mid = (l+r)>>1;if(f1[mid]+f2[i]+1 >= mx) l = mid+1,p = mid;else r = mid-1;}ans += sum[p] + p + f2[i]*p + (n-p)*mx;}printf("%lld\n",ans);return 0;
}

5. 5. 5. 权值统计

设 f [ u ] f[u] f[u] 表示 u u u 子树中的答案。分两种情况考虑 u u u。若 u u u 是路径的端点,那么答案就是 f [ u ] f[u] f[u]。如果 u u u 是路径上的点,答案就是 l s o n × r s o n × a [ u ] lson \times rson \times a[u] lson×rson×a[u]。

这里我们记录 ( ∑ v ⊆ s o n [ u ] f [ v ] ) 2 (\sum_{v\subseteq son[u]} f[v])^2 (∑v⊆son[u]​f[v])2 和 ∑ v ⊆ s o n [ u ] f [ v ] 2 \sum_{v\subseteq son[u]} f[v]^2 ∑v⊆son[u]​f[v]2,相减除以 2 2 2 就是答案。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c)  for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;
}
inline void print(int x){if(x < 0) putchar('-'),x = -x;if(x >= 10) print(x / 10);putchar(x % 10 + '0');
}
const int M = 2e5+10;
const int mod = 10086;
int head[M],a[M],f[M];
int n,cnt,ans;
struct edge{int to,nxt;
}e[M];
inline void add(int u,int v){e[++cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;
}
inline void dfs(int u,int fa){int s1 = 0,s2 = 0,s3 = 0;for(re int i(head[u]) ; i ; i=e[i].nxt){int v = e[i].to;if(v == fa) continue;dfs(v,u);s1 += f[v];s2 += f[v]*f[v];}f[u] = (s1+1) * a[u] % mod;s3 = ((s1*s1-s2)>>1) % mod;ans = (ans + f[u] + s3 * a[u]) % mod;
}
signed main(){n = read();rep(i,1,n) a[i] = read();rep(i,1,n-1){int u = read(),v = read();add(u,v),add(v,u);}dfs(1,0);printf("%lld\n",ans);return 0;
}

YbtOJ「动态规划」第4章 树形DP相关推荐

  1. 一直学不明白的「递归」与「动态规划」原来这么简单

    在学习「数据结构和算法」的过程中,因为人习惯了平铺直叙的思维方式,所以「递归」与「动态规划」这种带循环概念(绕来绕去)的往往是相对比较难以理解的两个抽象知识点. 程序员小吴打算使用动画的形式来帮助理解 ...

  2. 「图论」第1章 并查集课堂过关

    文章目录 A. [例题1][模板]并查集 题目 代码 B. [例题2]程序自动分析 题目 代码 C. [例题3]银河英雄传说 题目 题目背景 题目描述 输入格式 输出格式 输入输出样例 说明/提示 思 ...

  3. 「SDOI2016」储能表(数位dp)

    「SDOI2016」储能表(数位dp) 神仙数位 \(dp\) 系列 可能我做题做得少 \(QAQ\) \(f[i][0/1][0/1][0/1]\) 表示第 \(i\) 位 \(n\) 是否到达上界 ...

  4. LibreOJ #2478.「九省联考 2018」林克卡特树 树形dp+带权二分

    题意 给出一棵n个节点的树和k,边有边权,要求先从树中选k条边,然后把这k条边删掉,再加入k条边权为0的边,满足操作完后的图仍然是一棵树.问新树的带权直径最大是多少. n,k≤3∗105n,k≤3∗1 ...

  5. 「火」皇家烈焰 (线性DP)

    「火」皇家烈焰 题目描述 : 帕秋莉掌握了一种火属性魔法 由于钟爱扫雷游戏,帕秋莉把自己图书馆前的走廊看作一个一维的扫雷地图,她制造了很多烈焰,排在这条走廊内 现在帕秋莉告诉你一部分烈焰的分布情况,请 ...

  6. 面试必备算法题集之「动态规划」Ⅰ

    题目来源:LeetCode-腾讯-动态规划 题库链接:LeetCode传送门 前言 这份题集来源于 LeetCode-腾讯-动态规划 ,怀着想学习动态规划的心(带着害怕面试被问到的恐惧 )做了第一份D ...

  7. 排序字段设计_「原创」第四章、模型设计

    java进阶架构师",选择右上角"置顶公众号" 20大进阶架构专题每日送达 模型设计 回顾 前三章我们可以算是一脚迈进了Django的大门(也称入坑),因为程序员入坑第一 ...

  8. 「Swift」笔记第二章 Basic Operators

    2 第二章:基本操作符 逻辑操作符:AND(&&) 算术操作符(+,-,*,/等)会检测和拒绝数据溢出操作 2.1 术语 操作符有一元的,二元的,三元的 一元操作符对单一对象进行操作, ...

  9. 「Swift」笔记第一章:The Basic

    1 第一章:基础 提供C/OC所有基础数据类型:int,double,float,string,bool:还提供三种基础存储数据的结构:array,set,dict,详见Collection Type ...

最新文章

  1. 折线图表动画(历史进程效果)
  2. 所需依赖_注意细节,阿里架构师一文详解SpringDI的四种依赖注入方式
  3. 深度学习 2 机器学习 神经网络 卷积神经网络
  4. hdu 1142 记忆化搜索
  5. 卫生间防水应注意哪些问题?
  6. weblogic 调优参数
  7. mysql中字符串和数字的互转函数
  8. Java:Spring @Transactional工作原理
  9. html内容超出不自动滚动,16.css: overflow使用 例: 固定div大小,不让内容超出div
  10. 下载新版火狐后无法同步书签_火狐浏览器书签同步设置教程
  11. 联想计算机型号吧,lenovo全系列联想笔记本电脑型号对照表
  12. hbase metric 监控项
  13. win10系统找不到telnet服务器,win10系统找不到Telnet服务的技巧介绍
  14. js 浏览器永久保存数据:localStorage
  15. 生命密码:你的第一本基因科普书
  16. 资产管理计划份额转让研究(大资管长文)
  17. 从王自如和老罗的论战中我貌似懂得了点神马...
  18. 面向NNA 功能覆盖的精简操作集计算 (ROSC)
  19. WARNING: --master-data is deprecated and will be removed in a future version
  20. pycharm离线安装中文插件

热门文章

  1. 1168 -- 奋斗的小蜗牛
  2. 如何判断一个链表是否有环?
  3. java nextintln_「nextint」next()nextLine()以及nextInt()的区别及用法 - seo实验室
  4. 达人评测 i5 1340P和i5 13420H对比 酷睿i51340P和i513420H差距
  5. 初一上册数学用计算机进行运算,400道初一上册数学计算题,初一上册100道数学计算题及答案。...
  6. 福州市陈峰主席一行莅临链脉名片考察交流
  7. 软件测试—十三章集成测试
  8. 面试被问到岗时间,是越快越好吗?
  9. 不负光阴就是最好的努力,而努力就是最好的自己。
  10. 华为手机信息不弹屏了为什么_华为手机短信不提醒怎么办?华为手机短信提醒设置方法...