• E


    题目做法大概就是点分治然后背包

    前置知识

    点分治

    应用场景

    • 求树上距离为k的点对数|是否存在
    • 路径为k且有限制条件

    总之就是dfs暴力会超时的优化

    点分治第一步首先要找到一棵树的重心

    然后再根据重心来进行分治

    judge i 距离当前根为i的点是否存在

    dis i 点i与当前根的距离

    点分治模板

    题意

    给定一棵有 n 个点的树,询问树上距离为 k的点对是否存在。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e5 + 5,M=1e7+6;
    int n, m, u, v, w,Q[N],ans[N];
    int root,cnt,S,temp[N],dis[N],max_childrenpart[N],size[N];
    bool use[N],judge[M];
    // judge i 与当前根距离为i的路径是否存在
    // use 表示该点有没有被遍历过
    struct node
    {
        int link, w;
    };
    vector <node> f[N];
    queue <int> q;
    void find_root(int x,int fa)// 板子
    {
        max_childrenpart[x] = 0;
        size[x] = 1;
        for(auto i:f[x])
        {
            if(i.link==fa||use[i.link])
                continue;
            find_root(i.link, x);
            size[x] += size[i.link];
            max_childrenpart[x] = max(size[i.link],max_childrenpart[x]);
        }
        max_childrenpart[x] = max(max_childrenpart[x],S-max_childrenpart[x]);
        if(max_childrenpart[x]<max_childrenpart[root])
            root = x;
    }
    void get_dis(int x,int fa) //板子
    {
        temp[++cnt] = dis[x];
        for(auto i:f[x])
        {
            if(use[i.link]||i.link==fa)
                continue;
            dis[i.link] = dis[x] + i.w;
            get_dis(i.link, x);
        }
    }
    void solve(int root)//不同题不同
    {
        while(!q.empty())
            q.pop();
        for(auto i:f[root])
        {
            if(use[i.link])
                continue;
            cnt = 0; // 所有点距离的计数器
            dis[i.link] = i.w; // 第一次要初始化一下
            get_dis(i.link,root);//计算距离root节点的距离
            for (int j = 1; j <= cnt; j++)
            {
                for (int k = 1; k <= m;k++)//这里肯定是可以优化的 因为没优化re了(judge爆了
                {
                    if(Q[k]>=temp[j])//询问路径大于 遍历的节点到根的距离说明可能存在
                    {
                        if(judge[Q[k]-temp[j]])
                        {
                            // 如果非当前子树存在到根距离为Q[k]-dis[j]的点
                            // 说明答案存在
                            ans[k] = 1;
                        }
                    }
                }
            }
            for (int j = 1; j <= cnt;j++)
            {
                q.push(temp[j]);
                judge[temp[j]] = 1;
            }
        }
        while(!q.empty())
        {
            judge[q.front()] = 0;// 撤回
            q.pop();
        }
    }
    void divided(int x)//板子
    {
        use[x] = 1;
        judge[0] = 1;
        solve(x);
        for(auto i:f[x])
        {
            if(use[i.link])
                continue;
            // 初始化根节点
            root = 0;
            max_childrenpart=n+1;
            //反正一定要大不然可能不会更新,之前max_childrenpart=size[i.link]是错的
            S= size[i.link];
            find_root(i.link,0);
            divided(root);
        }
    }
    int main()
    {
        scanf("%d %d", &n, &m);
        for (int i = 1; i < n;i++)
        {
            scanf("%d %d %d", &u, &v, &w);
            f[u].emplace_back((node){v, w});
            f[v].emplace_back((node){u, w});
        }
        for (int i = 1; i <= m;i++)
            scanf("%d", &Q[i]);
        root = 0;
        S=max_childrenpart[root] = n;
        find_root(1,0);//先找到重心
        divided(root);//重心开始第一次分治
        for (int i = 1; i <= m;i++)
        {
            if(ans[i])
                printf("AYE
    ");
            else
                printf("NAY
    ");
        }
        return 0;
    }
    

    树形dp

    emmm应该是树上背包

    选课

    之前都是用组合背包来写的(虽说现在都忘光了)

    题意:

    给一个森林,每个点有点权,选子节点一定要先选父节点,求选m个节点的最大权值

    dp[i][j]代表i为根选j个节点的最大权值

    因为是个森林,先可以用虚拟节点0连接构成一棵树value[0]=0; 然后求dp[0][m+1];

    显然dp[i][1]=value[i],dp[i][j]=max(dp[i][j],dp[i][j-1]+dp[v][k]);v是i的子节点

    $jin[1,m+1],kin[0,j-1] ecause j-1-k>=0 $

    这状态方程意思就是先把i的子节点预定(j-1),然后再求从子节点选k个的最大值(dp[v][k])

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 305;
    int n, m, x, y,value[N],size[N],dp[N][N];
    bool use[N];
    vector<int> f[N];
    void dfs(int x)
    {
        use[x] = 1;
        size[x] = 1;
        dp[x][1] = value[x];
        for(auto i:f[x])
        {
            if(i==x||use[i])
                continue;
            dfs(i);
            size[x] += size[i];
            for (int j = m+1; j >= 1;j--)
            {
                // 背包倒序,因为此时还没有更新dp[i][k]就相当于没遍历到i这个子节点(就没有重复加i的权值)
                for (int k = 1; k <= j - 1;k++)
                    dp[x][j] = max(dp[x][j], dp[x][j - k] + dp[i][k]);
            }
        }
    }
    int main()
    {
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= n;i++)
        {
            scanf("%d %d", &x, &y);
            f[x].push_back(i);
            f[i].push_back(x);
            value[i] = y;
        }
        dfs(0);
        printf("%d", dp[0][m + 1]);
        return 0;
    }
    

    二叉苹果树

    题意:

    有一个根1,每个边有边权,选子节点的边之前一定要满足选的边中父节点有边连接,求选m条边的最大权值

    就是把点权变成了边权,dp也要变一下,因为一个点下面有多条边,dp[i][1]就不是一个确定的数,需要从dp[i][0]状态转移过来

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1000;
    int n, m, x, y, z,size[N],dp[N][N];
    bool use[N];
    struct node
    {
        int link, w;
    };
    vector<node> f[N];
    void solve(int x)
    {
        size[x] = 1;
        use[x] = 1;
        for(auto i:f[x])
        {
            if(i.link==x||use[i.link])
                continue;
            solve(i.link);
            size[x] += size[i.link];
            for (int j = m; j >=1;j--)
            {
                for (int k = 0; k <= j-1;k++)
                    dp[x][j] = max(dp[i.link][k] + i.w+dp[x][j-k-1], dp[x][j]);
            }
        }
    }
    int main()
    {
        scanf("%d %d", &n, &m);
        for (int i = 1; i < n;i++)
        {
            scanf("%d %d %d", &x, &y, &z);
            f[x].push_back((node){y, z});
            f[y].push_back((node){x, z});
        }
        solve(1);
        printf("%d
    ", dp[1][m]);
        return 0;
    }
    

    bitset 优化

    二进制的思想,虽然不知道为什么会快...二进制运算加的速?

    利用bitset可以优化一些状态只有0,1的题

    也就是说只牵扯到了能不能买,就是一个“是”和“否”的问题,也就是说这个背包并没有什么权值(只有“可以”和“不可以”)这样才能优化 也就是就是dp的值只有0、1

    利用二进制运算的 |可以代替max函数

    (bs=bs<< value 代表选择了权值为value这个点)

    (a|=b Leftrightarrow 取b或者不取b,如果取b则a=b否则a=a)

    bitset <M> dp[N];//自动多一维类似vector
    //dp[j][k]=dp[j-1][k-a[i]]等价
    //初始化
    dp[i].reset();
    dp[i][0]=1;
    
    dp[i]<<=a[i];
    //代表第a[i]位为1,也就是说选择了一个value为a[i]的点
    
    dp[j] |= (dp[j-1]<<a[i]);
    
    
    

    其他具体用法可以看这篇博客

    (还看到有的人初始化很神奇,直接bs[i]=1<==>bs[i].reset(), b[i][0]=1,虽说不知道会不会有什么奇怪的问题)

    E - Master of Subgraph

    正片开始

    题意

    给你T组数据,每组数据有n个点n-1条边联通,并且每个点都有点权,选一些联通的点使其点权和属于[1,m],如果(i in [1,m] 时有子图权值为i则答案的第i位为1,否则为0(i从1开始))

    (0<T<16 quad n in (1,3000) quad min (1,1e5) quad w_iin [0,1e5] )

    答案形如 长度为m的 100101 这种字符串 ,保证图是一棵树

    选择联通的点,说明选了第一个点,后面的点必须是它的子节点,并且要选择它子节点的子节点必须要先选它子节点的父节点(就是说把选的第一个节点看成根,做树上背包)

    根据点分治的特点(要么选根节点要么不选根节点)刚好与题意相同

    这里bitset有个小细节,因为w*n会爆内存,而只需要统计m以内的,所以大小开到m就够了(开到1e6居然就超内存了)

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 3e3 + 5,M=1e5+6;
    int n, m,T, u, v,w[N];
    int root,cnt,S,max_childrenpart[N],size[N];
    bool use[N];
    vector <int> f[N];
    bitset<M> ans, dp[N];//dp i 以i为根所选的节点编号
    
    void find_root(int x,int fa)// 板子
    {
        max_childrenpart[x] = 0;
        size[x] = 1;
        for(auto i:f[x])
        {
            if(i==fa||use[i])
                continue;
            find_root(i, x);
            size[x] += size[i];
            max_childrenpart[x] = max(size[i],max_childrenpart[x]);
        }
        max_childrenpart[x] = max(max_childrenpart[x],S-max_childrenpart[x]);
        if(max_childrenpart[x]<max_childrenpart[root])
            root = x;
    }
    void solve(int x,int fa)
    {
        dp[x] <<= w[x];//选择这个节点
        for(auto i:f[x])
        {
            if(use[i]||i==fa)
                continue;
            dp[i] = dp[x]; // 初始化一下
            solve(i,x);
            dp[x] |= dp[i];
        }
    }
    
    void divided(int x)//板子
    {
        use[x] = 1;
        dp[x].reset();// 全初始化为0
        dp[x][0] = 1;//初始化第0位为1
        solve(x,0);
        ans |= dp[x];// ans'加上'这个权值
        for(auto i:f[x])
        {
            if(use[i])
                continue;
            root = 0;
            max_childrenpart[root] = n + 1;
            S = size[i];//初始化
            find_root(i,0);
            divided(root);
        }
    }
    int main()
    {
        scanf("%d", &T);    
        while(T--)
        {
            ans.reset();
            scanf("%d %d", &n, &m);
            for (int i = 0; i <= n;i++)
            {
                f[i].clear();
                use[i] = 0;
                max_childrenpart[i] = 0;
            }
            for (int i = 1; i < n; i++)
            {
                scanf("%d %d", &u, &v);
                f[u].emplace_back(v);
                f[v].emplace_back(u);
            }
            for (int i = 1; i <= n;i++)
                scanf("%d", &w[i]);
            root = 0;
            S=max_childrenpart[root] = n;
            find_root(1,0);//先找到重心
            divided(root);//重心开始第一次分治
            for (int i = 1; i <= m;i++)
                printf("%d", (int) ans[i]);
            printf("
    ");
        }
        return 0;
    }
    /*
    2
    4 10
    1 2
    2 3
    3 4
    3 2 7 5
    6 10
    1 2
    1 3
    2 5
    3 4
    3 6
    1 3 5 7 9 11
    */
    
  • 相关阅读:
    ACM常用算法及练习(2)
    ACM常用算法及练习(1)
    ACM进阶计划
    《算法艺术与信息学竞赛》题目-提交方式对照表
    ACM之Java速成(4)
    ACM之Java速成(3)
    ACM之Java速成(2)
    ACM之Java速成(1)
    uva 11520
    uva 10755
  • 原文地址:https://www.cnblogs.com/cherrypill/p/13192841.html
Copyright © 2020-2023  润新知