[SMOJ1791]新建道路

97 篇文章 0 订阅
15 篇文章 0 订阅

题目描述

n 个结点,编号 1 至 n,一开始没有边。现在总共要新建 m 条边,构成一个图。每一条新建的边都是无向边。但是要满足如下的条件:

  1. 选择两个不同编号的结点 X Y ,在 X Y 之间建立一条边,前提是两个结点的编号的差不超过给定的参数 k,即 0<|XY|k 。注意:允许在 A B 之间建立多条边(即两个结点之间可以有重边)。

    • 当最终建完 m 条边之后,对于任意的一个结点 i,与结点 i 相连的边共有偶数条。注意:0 也被认为是偶数。

问题是:总共可以构造出多少种不同的图?答案模 1000000007。构出来的图可以是不连通的图,如果构出来的图的边有交叉,这些交叉边理解为互不干扰。

输入格式 1791.in

一行,三个整数:n,m,k 1n30 0m30 1k8

输出格式 1791.out

一个整数。

输入输出样例 1791.in 1791.out


这是整套题里颇有难度的一题。
虽然题目描述里出现了“结点”和“连边”等字眼,但却不难看出,本题与图论的联系不大。其实完全可以看作是 n 个顺次排成一行的点,这样会更好分析。看到题目,相信很容易想到是 DP。但如何恰当地描述状态,却并非易事。
经过多套 DP 题的练习,我们应该能够归纳得出:DP 的状态是与题目的限制和一些参数息息相关的。那么不妨来分析一下这题里面的限制。

首先,有连边的结点编号差在 K 以内,而且注意到 K 是比较小的。要有点敏锐的意识,是否可以进行状态压缩?
其次,建完边之后,任意结点的度数为偶数。
考虑到某个结点是可以向左向右连的,但这样就不好考虑 DP。不妨简化问题,统一考虑每个结点统一只向一边连。这样一来就可以有阶段地连,即一个点一个点进行考虑。

光有点肯定是不够的,还要有边。而且点的度数的奇偶性肯定也会有影响。综上所述,我们记 f[i][j][s] 表示前 i 个点,剩 j 条边,相邻 K 个点奇偶性为 s 这种状态的方案数。每次可以从点 i 往后面连边,比如往点 x 连了 y 条边(i<xi+k, yj ),得到的新问题就是 f[i][[jy][s 的 0 和 xi 位取反 ]
但是这样转移会出现一些问题。

如果一个结点往后面多个结点连出了边,显然只应该被考虑一次,但是在我们的转移中,会先连到后面第一个,再连到后面第二个,同时会先连到后面第二个,再连到后面第一个都是有可能的,也就是出现了重复。
要避免重复,肯定是加一些条件进行限制。不妨记 f[i][j][s][x] 表示前 i 个点,剩 j 条边,相邻 K 个点奇偶性为 s 这种状态,当前要连边只能从第 i+x 位开始连的方案数,这样就完美避开了重复。

但是,如果我们每次枚举一个点,再枚举往它连多少条边,是比较浪费时间的。完全可以用“一步步来”的思想方法,一次只连一条边,剩下如果还能多连,交给新问题去考虑。
注意边界的问题,当没有边时,如果相邻 k 个点的度数都是偶数则有一种方案,否则没有;当考虑完最后一个点,如果还剩有边,那么也是不合法的情况,没有方案。另外,在实现上,用记忆化搜索会更为方便。
时间复杂度为 O(n×m×2k×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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值