[BZOJ 5219] [Lydsy2017省队十连测]最长路径

122 篇文章 0 订阅
22 篇文章 0 订阅
BZOJ传送门

题目描述

在 Byteland 一共有 n n n 个城市,编号依次为 1 1 1 n n n,它们之间计划修建 n ( n − 1 ) 2 \frac{n(n−1)}{2} 2n(n1)条单向道路,对于任 意两个不同的点 i i i j j j,在它们之间有且仅有一条单向道路,方向要么是 i i i j j j,要么是 j j j i i i。换句话 说,这是一个 n n n 个点的竞赛图。

Byteasar 居住在 1 1 1 号城市,他希望从 1 1 1 号城市出发,沿着单向道路不重复地访问一些城市,使得访 问的城市数尽可能多。

请写一个程序,帮助 Byteasar 计算有多少种道路修建方式,使得从 1 1 1 号点出发的最长简单路径经过点数恰好为 k k k,由于答案可能很大,请对 P P P 取模输出。

输入输出格式

输入格式

第一行包含两个正整数 n , P n,P n,P,表示点数和模数。

输出

输出 n n n 行,第 i i i 行输出从 1 1 1 出发的最长简单路径经过点数恰好为 i i i 的竞赛图个数模 P P P

输入输出样例

输入样例#1:
2 233
输出样例#1:
1
1
输入样例#2:
3 233
输出样例#3:
2
2
4

img

解题分析

前六个点, 没啥好说的, 暴力打表即可 A C AC AC

然后竞赛图有个很好的性质:一定存在一条哈密尔顿路径。

所以我们的起点 1 1 1一定在这条路径上, 且我们可以把这条路径分为两个联通块: A A A B B B 1 ∈ B 1\in B 1B)。当然也存在 A A A不存在的情况。

我们可以设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 i i i个点, 最长路径长度为 j j j的竞赛图数量。

首先我们可以知道 d p [ i ] [ 1 ] = 2 ( n − 2 ) ( n − 1 ) 2 dp[i][1]=2^{\frac{(n-2)(n-1)}{2}} dp[i][1]=22(n2)(n1), 这是因为实际上我们只需要将所有 1 1 1号点的边都指向 1 1 1号点即可。

然后对于其他 j &lt; i j&lt; i j<i的情况, d p [ i ] [ j ] = d p [ j ] [ j ] × ( i − 1 j − 1 ) × 2 ( i − j ) ( i − j − 1 ) 2 dp[i][j]=dp[j][j]\times \binom{i-1}{j-1}\times 2^{\frac{(i-j)(i-j-1)}{2}} dp[i][j]=dp[j][j]×(j1i1)×22(ij)(ij1) d p [ j ] [ j ] dp[j][j] dp[j][j]考虑的 j j j个点边的连接方式, ( i − 1 j − 1 ) \binom{i-1}{j-1} (j1i1)考虑选取的点的种类, 相当于钦定 1 1 1号点, 从剩下 i − 1 i-1 i1个点里面选 j − 1 j-1 j1个点组成 B B B, 剩下组成 A A A的点集合内部的边乱连即可。

i = j i=j i=j的时候, 这个式子不管用了, 但我们可以知道, 包含 i i i个点的竞赛图总数是 2 i ( i − 1 ) 2 2^{\frac{i(i-1)}{2}} 22i(i1)个, 所以 d p [ i ] [ i ] = 2 i ( i − 1 ) 2 − ∑ j = 1 i − 1 d p [ i ] [ j ] dp[i][i]=2^{\frac{i(i-1)}{2}}-\sum_{j=1}^{i-1}dp[i][j] dp[i][i]=22i(i1)j=1i1dp[i][j]

我们可以 O ( N 2 ) O(N^2) O(N2)预处理组合数, O ( N 2 ) D P O(N^2)DP O(N2)DP即可AC。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <cctype>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MX 2050
ll pw[MX * MX], dp[MX][MX], C[MX][MX];
ll mod;
int n, bd;
int main(void)
{
	scanf("%d%lld", &n, &mod);
	bd = n * (n - 1) / 2; R int i, j;
	pw[0] = 1; for (i = 1; i <= bd; ++i) pw[i] = pw[i - 1] * 2 % mod;
	for (i = 0; i <= n; ++i) C[i][0] = 1;
	for (i = 1; i <= n; ++i)
	for (j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
	for (i = 1; i <= n; ++i)
	{
		dp[i][1] = pw[(i - 1) * (i - 2) / 2]; dp[i][i] = pw[i * (i - 1) / 2];
		for (j = 2; j < i; ++j) dp[i][j] = dp[j][j] * C[i - 1][j - 1] % mod * pw[(i - j - 1) * (i - j) / 2] % mod;
		for (j = 1; j < i; ++j) (dp[i][i] -= dp[i][j]) %= mod;
		dp[i][i] = (dp[i][i] + mod) % mod;
	}
	for (R int i = 1; i <= n; ++i) printf("%lld\n", dp[n][i]);
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值