题目描述
有 n 个结点,编号 1 至
n ,一开始没有边。现在总共要新建 m 条边,构成一个图。每一条新建的边都是无向边。但是要满足如下的条件:
- 选择两个不同编号的结点
X 和 Y ,在X 和 Y 之间建立一条边,前提是两个结点的编号的差不超过给定的参数k ,即 0<|X−Y|≤k 。注意:允许在 A 和B 之间建立多条边(即两个结点之间可以有重边)。
- 当最终建完 m 条边之后,对于任意的一个结点
i ,与结点 i 相连的边共有偶数条。注意:0 也被认为是偶数。问题是:总共可以构造出多少种不同的图?答案模 1000000007。构出来的图可以是不连通的图,如果构出来的图的边有交叉,这些交叉边理解为互不干扰。
输入格式 1791.in
一行,三个整数:
n,m,k 。 1≤n≤30 , 0≤m≤30 , 1≤k≤8 。
输出格式 1791.out
一个整数。
输入输出样例 1791.in 1791.out
这是整套题里颇有难度的一题。
虽然题目描述里出现了“结点”和“连边”等字眼,但却不难看出,本题与图论的联系不大。其实完全可以看作是
n
个顺次排成一行的点,这样会更好分析。看到题目,相信很容易想到是 DP。但如何恰当地描述状态,却并非易事。
经过多套 DP 题的练习,我们应该能够归纳得出:DP 的状态是与题目的限制和一些参数息息相关的。那么不妨来分析一下这题里面的限制。
首先,有连边的结点编号差在
其次,建完边之后,任意结点的度数为偶数。
考虑到某个结点是可以向左向右连的,但这样就不好考虑 DP。不妨简化问题,统一考虑每个结点统一只向一边连。这样一来就可以有阶段地连,即一个点一个点进行考虑。
光有点肯定是不够的,还要有边。而且点的度数的奇偶性肯定也会有影响。综上所述,我们记
但是这样转移会出现一些问题。
如果一个结点往后面多个结点连出了边,显然只应该被考虑一次,但是在我们的转移中,会先连到后面第一个,再连到后面第二个,同时会先连到后面第二个,再连到后面第一个都是有可能的,也就是出现了重复。
要避免重复,肯定是加一些条件进行限制。不妨记
但是,如果我们每次枚举一个点,再枚举往它连多少条边,是比较浪费时间的。完全可以用“一步步来”的思想方法,一次只连一条边,剩下如果还能多连,交给新问题去考虑。
注意边界的问题,当没有边时,如果相邻
k
个点的度数都是偶数则有一种方案,否则没有;当考虑完最后一个点,如果还剩有边,那么也是不合法的情况,没有方案。另外,在实现上,用记忆化搜索会更为方便。
时间复杂度为
总的来说,这道题还是在 DP 上对状态的恰当分析,还有对于限制的考虑,实现的时候想一想是否会出现自己没有考虑过的情况。只有细心,把细节都搞好,才能做好题目。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 30 + 3;
const int maxm = 30 + 3;
const int maxk = 8 + 3;
const int mod = 1000000007;
int n, m, k;
int dp[maxn][maxm][1 << maxk][maxk];
int dfs(int node, int edge, int set, int start) {
if (dp[node][edge][set][start] != -1) return dp[node][edge][set][start]; //记忆化
if (node == n) return !edge; //边界
dp[node][edge][set][start] = 0;
if (!(set & 1)) (dp[node][edge][set][start] += dfs(node + 1, edge, set >> 1, 1)) %= mod; //当前点度数为偶数,可以不再从当前边向后连了
if (edge) //还有边,可以往后某个点连 1 条
for (int i = start; i <= min(k, n - node - 1); i++) (dp[node][edge][set][start] += dfs(node, edge - 1, set ^ 1 ^ (1 << i), i)) %= mod;
return dp[node][edge][set][start];
}
int main(void) {
freopen("1791.in", "r", stdin);
freopen("1791.out", "w", stdout);
scanf("%d%d%d", &n, &m, &k);
memset(dp, -1, sizeof dp);
printf("%d\n", dfs(0, m, 0, 1));
return 0;
}