奇怪的道路——状压DP

题目描述

小宇从历史书上了解到一个古老的文明。这个文明在各个方面高度发达,交通方面也不例外。 
考古学家已经知道,这个文明在全盛时期有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条边,满足条件:

  1. 每个点的度为偶数。
  2. 每条边连接的顶点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$之间的连边。

  转移不是特别难(这里采用刷表):

  1. $i和i-k+l不连边,有f[i][j][s][l+1]+=f[i][j][s][l]$
  2. $i和i-k+l$连边,有$f[i][j+1][s$ $ oplus$ $1<<k$ $oplus$ $1<<l][l]+=f[i][j][s][l]$
  3. 考虑增加一个点,那么必须有:编号为$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];
}
原文地址:https://www.cnblogs.com/THRANDUil/p/11588372.html