• [JSOI2018]潜入行动


    [JSOI2018]潜入行动


    树形背包。

    这道题考察树形背包中枚举上下界(时间复杂度&代码实现)和细节上的一些东西。

    定义状态:(dp[u][i][0/1][0/1]),代表以结点(u)为根的子树中除了根节点其它节点均被监控用了恰好(i)个监控,其中第一个(0/1)代表该结点是否放了监控;第二个(0/1)该结点是否也被监控

    大概有这样一些方程:

    [dp[u][i+j][0][0]=sum_{vin son(u)} {dp[u][i][0][0]*dp[v][j][0][1]} ]

    这个式子代表该结点既不放监控也不被子结点监控(换言之,想要合法父结点必须放监控),那么子结点肯定不能放监控,并且为了保证状态合法性,必须被监控。

    还有一点:为什么表达式中用(i+j)表示被更新状态恰恰好使用的监控数呢?

    那是因为(其实您如果写成其他的形式也都可以)这样的表示方便最终的枚举。换言之,枚举的边界方便“卡”。

    同理:

    [dp[u][i+j][0][1]=sum_{vin son(u)}{dp[u][i][0][0]*dp[v][j][1][1]+dp[u][i][0][1]*(dp[v][j][0][1]+dp[v][j][1][1])} ]

    这个式子利用树形背包的树合并思想:合并两个“泛化物品”即可。

    那么,我们对于当前泛化物品讨论:1. 如果当前的泛化物品根节点没有被监控,那么它的子结点放。

    1. 如果被监控了,那就都可以,放和不放两种情况一同记下来。

    还能得到:

    [dp[u][i+j][1][0]=sum_{vin son(u)}{dp[u][i][1][0]*(dp[v][j][0][0]+dp[v][j][0][1])} ]

    它的儿子不能放监控,然而它的子结点可以被监控也可以不被。

    最后&有:

    [dp[u][i+j][1][1]=sum_{vin son(u)}{dp[u][i][1][0]*(dp[v][j][1][0]+dp[v][j][1][1])+dp[u][i][1][1]*(dp[v][j][0][0]+dp[v][j][0][1]+dp[v][j][1][0]+dp[v][j][1][1])} ]


    边界条件(注意点):

    • 显然,如果该结点是叶子节点,那么其他都是零,只有:(dp[u][1][1][0]=1)(dp[u][0][0][0]=1)

    • 上述方程中(i)(j)中,(i(i<=K))不能超过当前(u)的总结点数(因为是动态合并的嘛),(j(j<=K))不能超过(v)的总结点数,并且(i+j<=K)

    • 转移的时候要建立一个临时数组(避免各种玄学错误、调试方便)

    代码如下:

    #include<iostream>
    #include<cstring>
    #include<vector>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    
    typedef long long LL;
    
    const int N = 1e5 + 5, K = 100 + 5, mod = 1e9 + 7;
    
    template <typename T> void read(T &x)
    {
    	bool mark = false;
    	char ch = getchar();
    	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
    	for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
    	if(mark) x = -x;
    	return;
    }
    
    vector <int> G[N];
    
    int n, lim[N] = {}, k, newdp_u[K][2][2], dp[N][K][2][2] = {};
    
    inline int add(int x, LL y)
    {
    	x %= mod, y %= mod;
    	y = (y + mod) % mod;
    	x += y;
    	while(x >= mod) x -= mod; 
    	return x;
    }
    
    void dfs(int u, int Fa)
    {
    	int n_lim = 1, v;
    	lim[u] = dp[u][0][0][0] = dp[u][1][1][0] = 1;
    	for(int cur = 0; cur < G[u].size(); ++ cur) 
    	{
    		v = G[u][cur];
    		if(v == Fa) continue;
    		dfs(v, u);
    		n_lim = min(k, lim[u] + lim[v]);//套路设置:总限制 
    		for(int i = 0; i <= lim[u]; ++ i)
    			for(int j = 0; j <= lim[v] && i + j <= n_lim; ++ j)
    				{
    					newdp_u[i + j][0][0] = add(newdp_u[i + j][0][0], 1ll * dp[u][i][0][0] * dp[v][j][0][1] % mod); 
    					newdp_u[i + j][1][0] = add(newdp_u[i + j][1][0], 1ll * dp[u][i][1][0] * (dp[v][j][0][0] + dp[v][j][0][1]) % mod);
    											
    					newdp_u[i + j][0][1] = add(newdp_u[i + j][0][1], 1ll * dp[u][i][0][0] * dp[v][j][1][1] % mod + 1ll * dp[u][i][0][1] * 
    											(dp[v][j][0][1] + dp[v][j][1][1]) % mod);
    
    					newdp_u[i + j][1][1] = add(newdp_u[i + j][1][1], 1ll * dp[u][i][1][0] * (dp[v][j][1][0] + dp[v][j][1][1]) % mod + 1ll * dp[u][i][1][1] * 
    											((dp[v][j][0][0] + dp[v][j][0][1]) % mod + (dp[v][j][1][0] + dp[v][j][1][1]) % mod) % mod); 
    				}
    		for(int i = 0; i <= n_lim; ++ i) 
    			for(int x = 0; x < 2; ++ x)
    				for(int y = 0; y < 2; ++ y)
    					dp[u][i][x][y] = newdp_u[i][x][y], newdp_u[i][x][y] = 0;
    		lim[u] = n_lim;
    	}
    	return;
    }
    int main()
    {
    	read(n), read(k);
    	int u, v;
    	for(int i = 0; i <= n; ++ i) G[i].clear();
    	for(int i = 1; i < n; ++ i)
    	{
    		read(u), read(v);
    		G[u].push_back(v), G[v].push_back(u);
    	}
    	dfs(1, 0);
    	printf("%d
    ", (dp[1][k][0][1] + dp[1][k][1][1]) % mod);
    	return 0;
    }
    
  • 相关阅读:
    HTML5 自动聚焦 属性
    c语言学习笔记(2)——预备知识
    java实现文件的上传和下载
    拓网超大文件上传组件
    java+下载+大文件断点续传
    word内容带样式粘贴到富文本编辑器
    php上传整个文件夹
    超大文件上传系统
    java+web中上传文件夹
    vue上传文件
  • 原文地址:https://www.cnblogs.com/zach20040914/p/14294084.html
Copyright © 2020-2023  润新知