题目描述
小宇从历史书上了解到一个古老的文明。这个文明在各个方面高度发达,交通方面也不例外。
考古学家已经知道,这个文明在全盛时期有n座城市,编号为1..n。m条道路连接在这些城市之间,每条道路将两个城市连接起来,使得两地的居民可以方便地来往。
一对城市之间可能存在多条道路。 据史料记载,这个文明的交通网络满足两个奇怪的特征。
首先,这个文明崇拜数字K,所以对于任何一条道路,设它连接的两个城市分别为u和v,则必定满足1 <=|u - v| <= K。此外,任何一个城市都与恰好偶数条道路相连(0也被认为是偶数)。
不过,由于时间过于久远,具体的交通网络我们已经无法得知了。
小宇很好奇这n个城市之间究竟有多少种可能的连接方法,于是她向你求助。
方法数可能很大,你只需要输出方法数模1000000007后的结果。
100%的数据满足1 <= n <= 30, 0 <= m <= 30, 1 <= K <= 8.
简化版题意:n个点m条边,满足条件:
- 每个点的度为偶数。
- 每条边连接的顶点u,v编号之差不超过K且没有自环。
求方案数%1000000007后的值。
思路
挺神的一道状压$DP$题。难点在于状态量的表示。首先我们分析数据范围,发现$K<=8$,那么很显然状态压缩的那一维和K有关,也是我们直接想到状压$DP$的一个原因。
那么有$f[i][j][s]$表示前$i$个点,连了$j条边,编号为i-k->i$的点的状态为$s$,那么显然$s$表示的是这些点度数的奇偶性。
然后快乐的开始推。考虑加入一条边的状态转移。然后,就没有然后了。。。。。。(本人做此题也就到此为止了)
怎么加边?以$i$为一个端点,另一个呢?很显然这个状态不足以满足$DP$转移,我们需要再加一维,表示当前的$i$向哪个点连边。又因为顶点编号之差$<=k$,我们只需要考虑$i向区间[i-k,i-1]$的连边就可以了。那么有$f[i][j][s][l]表示前i个点,连了j条边,[i-k,i]的状态为s,处理当前点i和i-k+l$之间的连边。
转移不是特别难(这里采用刷表):
- $i和i-k+l不连边,有f[i][j][s][l+1]+=f[i][j][s][l]$
- $i和i-k+l$连边,有$f[i][j+1][s$ $ oplus$ $1<<k$ $oplus$ $1<<l][l]+=f[i][j][s][l]$
- 考虑增加一个点,那么必须有:编号为$i-k$的点度数为偶数,$[i-k,i-1]$区间的点和i已经全部转移
答案就是$f[n][m][0][k]$前$n$个点连接了$m$条边,当且处理的是$n-k+k=n$,即$[n-k,n-1]$全部处理完的情况。
code
#include<bits/stdc++.h>
using namespace std;
const int p=1000000007;
const int S=1<<9;
int f[35][35][S][35];
int n,m,k;
int main()
{
scanf("%d%d%d",&n,&m,&k);
f[2][0][0][0]=1;//初始化,1,2间没有连边也是一种方案
for(int i=2;i<=n;i++)
for(int j=0;j<=m;j++)
for(int s=0;s<(1<<k+1);s++)//枚举状态
{
for(int l=0;l<k;l++)//枚举i-k+l
{
f[i][j][s][l+1]+=f[i][j][s][l]%=p;//不连边
if(i-k+l>0&&j<m)f[i][j+1][s^(1<<l)^(1<<k)][l]+=f[i][j][s][l]%=p;//连边
}
if(!(s&1))f[i+1][j][s>>1][0]+=f[i][j][s][k]%=p;//把i-k删去,加入i+1
}
cout<<f[n][m][0][k];
}