洛谷 P6239 [JXOI2012] 奇怪的道路 题解

发布时间 2023-11-24 19:09:19作者: gsczl71

P6239 [JXOI2012] 奇怪的道路

首先,拿到题面,\(n \le 30\)\(k \le 8\)这不就暴搜吗。再想想,紫题会给你暴搜的机会吗?所以进一步思考,发现这其实是一道 DP,而且数据这么小,肯定是给状压 DP 的样子。

经过一定思考,发现我们可以直接线性枚举 \([1,n]\)

为什么呢?因为每一个点只能和前 \(k\) 个点连边,而且每一个点只能有偶数条连边。那么我们枚举到 \(i\),它可以改变的点最前面是 \(i-k\),它下一个就是 \(i+1\),那么枚举到 \(i+1\) 时肯定没办法再改变 \(i-k\),所以,到了 \(i\) 是改变 \(i-k\) 最后一次机会,所以再此时必须改变 \(i-k\) 变成偶数。

于是,我们可以设计 dp 状态 \(dp_{i,j,l,z}\) 代表枚举点数 \(i\),枚举边 \(j\),前 \(k\) 个点中的第 \(l\) 个,\(z\) 为状态压缩,代表着每一个点的奇偶性。

因此我们可以推出转移方程(见代码)。

注意事项:状压 DP 用到位运算,因为位运算的优先级复杂,建议勤快的加括号,我就是因为这个括号的问题调了很久

AC CODE:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,k;
int dp[40][40][20][550];
const int p=1e9+7;
//定义:dp[i][j][l][z]
//表示枚举到第i个点,第j条边,z为i个点与前k个点的状压连边状态,已经连了l条边
signed main(){
	cin >> n >> m >> k;
	dp[2][0][0][0]=1;
	for(int i = 2;i <= n;i++){
		//枚举每一个点
		for(int j = 0 ;j <= m;j++){	
			//枚举每一条边
			for(int z=0;z<(1<<(k+1));z++){
				//枚举状态
				for(int l = 0;l <= k;l++){
					//枚举已经连的边数
					if(l!=min(i-1,k)){
						dp[i][j][l+1][z]=(dp[i][j][l+1][z] + dp[i][j][l][z])%p;
						dp[i][j+1][l][z^(1<<(l+k-min(k,i-1)))^(1<<k)] = (dp[i][j+1][l][z^(1<<(l+k-min(k,i-1)))^(1<<k)] + dp[i][j][l][z]) % p;
					}
					if(l==min(i-1,k) && !(z & 1))
						dp[i+1][j][0][z>>1]=(dp[i+1][j][0][z>>1]+dp[i][j][l][z])%p;
					
				}
			}
		}
	}
	cout<<dp[n+1][m][0][0] % p;
	return 0;
}