第二周 树形结构(x) 树形dp(√)

发布于:2022-12-15 ⋅ 阅读:(455) ⋅ 点赞:(0)

本来找的是树形结构,没想到几乎都是树形dp。。。

1.最大子树和 - 洛谷

屑小明,给你一拳。(小明见问题被轻易攻破,相当不爽,于是又拿来问你。)

树形dp,找点权之和最大的一颗子树的点权和而且子树不能为空,以1为根节点,儿子的点权为负时剪枝就可以了。

#include <bits/stdc++.h>
using namespace std;
int i,j,n;
int a[16010],b[16010];
vector<int>v[16010];
void dfs(int x, int y) {
    b[x]=a[x];
    for(int i=0;i<v[x].size();i++) {
        int t=v[x][i];
        if (t!=y){
            dfs(t,x);
            if (b[t]>0) b[x]+=b[t];
        }
    }
}
int main() {
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<n;i++) {
        int x,y;
        scanf("%d%d",&x,&y);
        v[x].push_back(y);
        v[y].push_back(x);
    }
    dfs(1,0);
    int ans=b[1];
    for(int i=1;i<=n;i++){
        if(b[i]>ans) ans=b[i];
    }
    printf("%d",ans);
}

2.遍历问题 - 洛谷

树形结构但不dp,是个二叉树,判断遍历中存在着多少个只有一棵子树的情况,不给中序遍历是因为中序遍历会确定左子树和右子树的情况,而仅靠前序和后序是没办法确定左右的,通过前序与后序的比对可以确定存在只有一子树的数量sum,因为不确定左右,所以sum*2就行了。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int ans;
char str1[10010],str2[10010];
int main(){
    scanf("%s",str1);
    scanf("%s",str2);
    for(int i=0;i<strlen(str1);i++)
         for(int j=1;j<strlen(str2);j++)
          if(str1[i]==str2[j]&&str1[i+1]==str2[j-1])
           ans++;
    printf("%d",1<<ans);
    return 0;
}

3.有线电视网 - 洛谷

树形dp,把每个节点都看成一个背包,容量是以这个节点为根的子树大小,组数就是连接的儿子个数,dp[u][j]=max(dp[u][j],dp[u][j-i]+dp[v][i]-edge[k].w),最后输出dp[1][i]>=0时i的最大值。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,k;
int head[3010],val[3010],dp[3010][3010]; 
struct node{
    int to,next,w;
}edge[10000010];
void adde(int u,int v,int w)
{
    edge[++k].to=v;
    edge[k].next=head[u];
    edge[k].w=w;
    head[u]=k;
}
int dfs(int u)
{
    if(u>n-m)
    {
        dp[u][1]=val[u];
        return 1;
    } 
    int sum=0,t;
    for(int k=head[u];k;k=edge[k].next)
    {
        int v=edge[k].to;
        t=dfs(v);
        sum+=t;
        for(int j=sum;j>0;j--)
        {
            for(int i=1;i<=t;i++)
            {
                if(j-i>=0) dp[u][j]=max(dp[u][j],dp[u][j-i]+dp[v][i]-edge[k].w);
            }
        }
    }
    return sum;
}
int main()
{
    memset(dp,~0x3f,sizeof(dp));
    scanf("%d%d",&n,&m);
    for(int u=1;u<=n-m;u++)
    {
        int size,v,w;
        scanf("%d",&size);
        for(int j=1;j<=size;j++)
        {
            scanf("%d%d",&v,&w);
            adde(u,v,w);
        }
    }
    for(int i=n-m+1;i<=n;i++) scanf("%d",&val[i]);
    for (int i=1;i<=n;i++) dp[i][0]=0;
    dfs(1);
    for (int i=m;i>=1;i--)
        if (dp[1][i]>=0)
        {
            printf("%d",i);
            break;
        }
    return 0;
}

4.医院设置 - 洛谷

树形dp,实际是找数的重心,就是每个节点到重心的距离和最小,这道题需要再乘以节点的权值,先以1为根,求出f[1],即每个点到1的距离之和,在通过转移重心,输出最小的值即可。

重心转移:f[v]=f[u]+(sze[1]−sze[v])−sze[v],sze[u]表示以u为根的子树的大小,v为u的子节点。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
long long int w[110],ans,f[110];
int n,i,j,num;
int head[10010],sze[10010];
struct node{
    int to,next;
}e[101];
inline void add(int from, int to){
    e[++num].to = to;
    e[num].next = head[from];
    head[from] = num;
}
void dfs(int u, int fa, int dep){ 
    sze[u] = w[u];
    for(int i = head[u]; i; i = e[i].next){
       if(e[i].to != fa)
         dfs(e[i].to, u, dep + 1), sze[u] += sze[e[i].to];
    }
    f[1] += w[u] * dep;
}
void dp(int u, int fa){
    for(int i = head[u]; i; i = e[i].next)
       if(e[i].to != fa)
         f[e[i].to] = f[u] + sze[1] - sze[e[i].to] * 2, dp(e[i].to, u);
    ans = min(ans, f[u]); 
}
int main(){
    ans=10000000;
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        int b,c;
        scanf("%d%d%d",&w[i],&b,&c);
        if(b){
            add(i,b);add(b,i);
        }
        if(c){
            add(i,c);add(c,i);
        }
    }
    dfs(1,0,0);
    dp(1,0);
    printf("%lld",ans);
    return 0;
}

5.会议 - 洛谷

树形dp,依旧是重心的转移,求f[1],然后dfs,求 f[now]=f[fa]+n-2*size[now],fa是now的父节点,n也就是size[1]啦。

#include <cstdio>
int d[50010],f[50010];
int n,cnt;
int size[50010];
bool vis[50010];
int head[50010];
struct Edge{
    int to,nxt;
}edge[100010];
void add(int x,int y){
    edge[++cnt].to=y;
    edge[cnt].nxt=head[x];
    head[x]=cnt;
}
void dfs1(int now){
    size[now]=1;
    for(int i=head[now];i;i=edge[i].nxt){
        int to=edge[i].to;
        if(d[to]) continue;
        d[to]=d[now]+1;
        dfs1(to);
        size[now]+=size[to];
    }
}
void dfs(int now,int fa){
    f[now]=f[fa]+n-2*size[now];
    for(int i=head[now];i;i=edge[i].nxt){
        int to=edge[i].to;
        if(to==fa) continue;
        dfs(to,now);
    }
}
int main(){
    scanf("%d",&n);
    for(int x,y,i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    d[1]=1;
    dfs1(1);
    int maxn=0,idx=1;
    for(int i=1;i<=n;i++) maxn+=d[i];
    maxn-=n;
    f[1]=maxn;
    for(int i=head[1];i;i=edge[i].nxt){
        int to=edge[i].to;
        dfs(to,1);
    }
    for(int i=2;i<=n;i++){
        if(f[i]<maxn) maxn=f[i],idx=i;
    }
    printf("%d %d",idx,maxn);
    return 0;
}

6.扩散 - 洛谷

寻找给出的坐标中两个距离最远且中间没有其他点的两个点,然后除以2,向上取整。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n,maxx,ans;
int x[55],y[55];
int g[55][55];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
      cin>>x[i]>>y[i];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            g[i][j]=abs(x[i]-x[j])+abs(y[i]-y[j]);
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                g[i][j]=min(max(g[i][k],g[k][j]),g[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            maxx=max(maxx,g[i][j]);
    ans=(maxx+1)/2;
    cout<<ans<<endl;
    return 0;
}

7.战略游戏 - 洛谷

树形dp,其实就是选与不选的问题,对于一个点,选了它那么它的子节点都不用选,不选它就要选它的所有子节点,说白了就是染色,设f[2000][2],一种存选的情况,一种存不选,最后从两者中选小的输出。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
struct node{
    int to,nxt;
}e[20000];
int n,i,j,cnt;
int head[2000],f[2000][2];
void add(int a,int b){
    e[++cnt].nxt=head[a];
    e[cnt].to=b;
    head[a]=cnt;
}
void dfs(int u,int fa){
    f[u][1]++;
    for(int i=head[u];i;i=e[i].nxt){
        int t=e[i].to;
        if(t==fa) continue;
        dfs(t,u);
        f[u][0]+=f[t][1];
        f[u][1]+=min(f[t][0],f[t][1]);
    }
}
int main(){
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        int a,b,k;
        scanf("%d %d",&a,&k);
        for(j=1;j<=k;j++){
            scanf("%d",&b);
            add(a+1,b+1);add(b+1,a+1);
        }
    }
    dfs(1,0);
    printf("%d",min(f[1][1],f[1][0]));
    return 0;
}

8.让我们异或吧 - 洛谷

不是树形dp,xor有一个性质:x ^ y ^ y = x,就是说将一个数xor两次同一个数后还是那个数,所以就直接dfs,在dfs中处理u,v到根节点的异或值,然后f[u]^f[v]。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring> 
using namespace std;
int n,m,k=0,head[100005],f[100005],vis[100005];
struct node{
int to,next,w;
}e[200005];
void add(int u,int v,int w)
{
    e[++k].to=v;
    e[k].next=head[u];
    e[k].w=w;
    head[u]=k;
}
void dfs(int u,int fa)
{
    f[u]=fa;vis[u]=1;
    for(int i=head[u];i;i=e[i].next)
        if(!vis[e[i].to])
            dfs(e[i].to,fa^e[i].w);
}
int main()
{
    scanf("%d",&n);
    int u,v,w;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w); 
    }
    dfs(1,0);scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        printf("%d\n",f[u]^f[v]);
    }
    return 0;
}