zoukankan      html  css  js  c++  java
  • hihoCoder #1471 拥堵的城市

    这道题目是hihoCoder Challenge 27的C题,我考虑了5天:(。
    计数问题。由于树的结构的特殊性(树具有递归结构),不难想到思路是树形DP。由于这是【计数问题】而非【优化问题】,我们思考时应该着重考虑两个基本的计数原理:加法原理与乘法原理,而非所谓【最优子结构】。

    DP 状态

    关于DP状态设计,我的思考过程如下:
    指定一节点作为根,转化成有根树。我们考虑子问题:

    子树 $v$ 中的路线方案数。

    然后考虑

    将子树 $v$ 中的所有可能的方案压缩到一个怎样的子空间中能实现从(子树) $v$ 到其父节点 (子树)$u$ 的状态转移?

    DP状态要有助于计算通过 $u$ 的路线的方案数。而这样的路线由任意两不同的自由节点(非路线端点) $v_1$、$v_2 ext{ s.t. } ext{LCA}(v_1,v_2) = u$ 所确定。

    状态1
    $dp[i][j]$:子树 $i$ 中有 $j$ 个自由点的方案数。

    然而这 $j$ 个自由点到点 $i$ 的路径在【任意一条边最多被 $k$ 条路线经过】的限制下可能互斥。

    状态2

    在 $dp[i][j]$: 从子树 $i$ 中选择 $j$ 条【独立路径】伸出来的方案数。

    这个状态是可以转移的。这里需要指出的一点是,我们不采用类似于 “子树 $i$ 中有 $j$ 条独立路径伸出来” 这样的表述来定义状态,而采用 “选择” 这个词。

    转移方程

    同大多数树形 DP 一样,状态转移是一个合并子树的过程。在计算子树 $u$ 中的方案数的过程中,对于 $u$ 的儿子 $v$,将子树 $v$ 的状态合并到子树 $u$ 中的方法:

    枚举从子树 $v$ 中选出的【用于和前面的独立路径配对的独立路径】的数目 $x$ 和【不配对的独立路径】的数目 $y$

    Implementation

    #include <bits/stdc++.h>
    using namespace std;
    
    using ll = long long;
    
    const int mod = 1e9+7;
    
    const int N = 305;
    
    vector<int> g[N];
    int size[N];
    
    int dp[N][N];
    
    
    void add_mod(int &x, int y)
    {
    	x += y;
    	if(x >= mod)
    		x -= mod;
    }
    
    int f[N]{1};
    int c[N][N];
    
    void prep(int n)
    {
    	for(int i=1; i<=n; ++i)
    		f[i]=ll(f[i-1])*i%mod;
    
    	for(int i=0; i<=n; i++)
    		c[i][0] = 1;
    	for(int i=1; i<=n; i++)
    		for(int j=1; j<=i; j++)
    			add_mod(c[i][j], c[i-1][j-1]),  add_mod(c[i][j], c[i-1][j]);
    }
    
    int cur[N];
    
    int n, k;
    
    void dfs(int u, int fa)
    {
    	size[u] = 1;
    	dp[u][0] = 1;
    	dp[u][1] = 1;
    
    	for(auto &v: g[u])
    	{
    		if(v != fa)
    		{
    			dfs(v, u);
    			for(int x=0; x <= min(size[v], k); ++x)	// pair
    			{
    				for(int y=0; x+y <= min(size[v], k); ++y)	// single
    				{
    					int tmp = ll(dp[v][x+y]) * c[x+y][y] % mod;
    					
    					if(tmp == 0)
    						continue;
    
    					for(int i=x; i<=size[u]; ++i)
    					{
    						add_mod(cur[i-x+y], ll(tmp) * dp[u][i] % mod * c[i][x] % mod * f[x] % mod);	// error-prone
    					}
    				}
    			}
    
    			size[u] += size[v];
    
    			for(int i=0; i<=size[u]; ++i)
    			{
    				dp[u][i] = cur[i];
    				cur[i] = 0;
    			}		
    		}
    	}
    }
    
    int main()
    {
    	scanf("%d%d", &n, &k);
    	prep(n);
    	for(int i=1; i<n; i++)
    	{
    		int u, v;
    		scanf("%d%d", &u, &v);
    		g[u].push_back(v);
    		g[v].push_back(u);	
    	}
    
    	dfs(1, 1);
    	
    	printf("%d
    ", dp[1][0]);
    	return 0;
    }
    

    要格外注意递归函数中的全局变量。

    总结

    前两天在知乎上看到一个解释【DP状态的无后效性】的回答,思考这个问题的过程中感到很受用。

    一般我们说的最优解问题,最朴素的做法是搜索所有的方案,大部分时候这个复杂度是不能接受的。对某些问题来说,我们可以将问题分解成子问题,子问题具有最优子结构的特点:对子问题的优化可以带来对整个问题的优化。但仅仅是这样并没有很大的帮助,因为我们对子问题的优化可能会导致整个问题的其他部分的解法变得不成立,或者变得不够优化,这样我们仍然不能有效计算整个问题的最优解。但是,某些情况下,子问题对于整个问题的影响,可以归纳到一个比“所有子问题解法”的空间小得多的“子问题状态”空间中,只要子问题解决后的状态处于某个状态,它对于整个问题的影响就是一致的,这样如果已知子问题处于某个状态,我们就可以求出这个状态条件下子问题的最优解,当全局最优且这个子问题取这个状态时,子问题本身的解法一定是我们刚刚求出的最优解。这样我们只需要对每个子问题的每个状态计算最优解,然后通过这些最优解的递推关系就可以求出整个问题的最优解。这种子问题对主问题的影响可以总结为少数“状态”的特性就叫做无后效性,意味着只要状态一致,具体经过怎样的步骤到达这一状态是没有影响的。
    SOURCE

  • 相关阅读:
    AnaConda环境下安装librosa包超时
    [浙江大学数据结构]多项式求值,及算法效率问题
    java正则表达式测试用例
    tK Mybatis 通用 Mapper 3.4.6: Example 新增 builder 模式的应用
    Detect image format in Java(使用java探测图片文件格式)
    使用ColumnType注解解决/过滤/转义tk mybatis插入insertSelective、insert语句中遇到sql关键字
    IDEA中关闭sonar代码质量检测
    pip设置安装源
    无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系
    sql 查出一张表中重复的所有记录数据
  • 原文地址:https://www.cnblogs.com/Patt/p/6441025.html
Copyright © 2011-2022 走看看