E - Master of Subgraph

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

前置知识

点分治

应用场景

  • 求树上距离为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\n");
        else
            printf("NAY\n");
    }
    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的子节点

$j\in[1,m+1],k\in[0,j-1] \because 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\n", 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 m\in (1,1e5) \quad w_i\in [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("\n");
    }
    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
*/
posted @ 2020-06-25 20:31  Sakura_Momoko  阅读(167)  评论(0编辑  收藏  举报