ProblemD. DP
input:dp.in output: dp.out
动态规划(英语:Dynamicprogramming,DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
(摘自Wikipedia)
同时,JKi拥有自己的对于动态规划算法的理解:他认为动态规划包含两个关键因素,一个被称为状态,另一个被称为决策。状态可以理解为一个问题在一个局部条件下的最优解,决策则可以理解为状态之间转移(逐步放宽局部条件后的最优解的改变)的方案。
甚至,他认为所有算法问题都可以状态和决策进行处理;例如朴素算法便是尝试了所有的状态以及决策。所谓的算法优化也是对于状态的冗余(限制了无关仅要的条件)和决策的冗余(进行了无关紧要的状态转移尝试)进行尝试性的去除。
接下来的这个问题将会有多种算法可以解决,你想使用DP么?(并非一种建议)
您将统计在一个N个节点(被编号为1-N)的完全无向图中(任意两个不相同的节点都有一条无向边),在有M条边被禁止使用(无向边,若u-v被禁止使用则u->v和v->u都无法直接通行)的情况下图中存在多少个不同的哈密顿回路。
一个哈密顿回路是图中的一个个环,访问每个顶点恰好一次。包含相同边一个环在该问题中被要求只统计一次。例如,1->2->3->4->1 1->4->3->2->1和2->3->4->1->2都相同,但1->3->2->4->1是不同的。
Input
输入第一行一个整数T(T<=10)代表输入数据组数
每组数据第一行两个整数N,M(1<=N<=10,0<=M<=15),分别表示完全无向图中的节点数量和被禁止使用的无向边的数量。
每组数据第二行起共计M行,每行两个整数u,v,描述一条被禁止使用的无向边(即u-v被禁止使用)。输入数据保证没有重复的边。(u,v∈[1,N] && u!=v)
Output
每组输出一行:首先输出Case #C: (C代表数据编号,从1开始直至T);紧跟一个空格符后输出满足条件的哈密顿回路数量.
SampleInput
2
4 1
1 2
8 4
1 2
2 3
4 5
5 6
SampleOutput
Case #1: 1
Case #2: 660
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define N 21
#define S 2100
int n,m,top,ans; //u,v为禁止边的两个节点,top,ans为最终结果
int f[S][N],c[N][N]; //c[i][j]即节点为i、j的边是否被禁用(0-未禁用,1-禁用)
int bit[N]; //按位存储状态压缩
//loop variable
int _T,i,cnt,msk,u,v;
int main(){
freopen("dp.in","r",stdin);freopen("dp.out","w",stdout);
for(i=0;i<N;i++)bit[i]=1<<i; //状态压缩,bit[]={1,2,4,8,16,...}
int T;scanf("%d",&T);
for(_T=1;_T<=T;_T++){
printf("Case #%d: ",_T);
scanf("%d%d",&n,&m); //节点总数、禁止使用边数
memset(c,0,sizeof(c)); //初始化0,为所有边当前可用
for(i=0;i<m;i++){
scanf("%d%d",&u,&v); //对于每一个禁止使用的边
u--;v--; //节点下标比题中节点命名方式少1
c[u][v]=c[v][u]=1; //标记该边为1,即禁用
}
memset(f,0,sizeof(f));
f[1][0]=1;top=bit[n]-1; //top为上限值:全节点全为1时
for(cnt=1;cnt<n;cnt++){
for(msk=bit[cnt]-1;msk<=top;){ //msk初始值为cnt位之前全置1
for(u=0;u<n;u++){ //遍历i节点
if(!f[msk][u])continue; //如果当前节点
for(v=0;v<n;v++){ //遍历j节点
if(msk&bit[v])continue; //与运算得出 msk中bit[v]状态是否为1
if(c[u][v])continue; //此边若已被禁止则跳过
f[msk|bit[v]][v]+=f[msk][u]; //msk|bit[v]:将msk的第v位数置为1
}
}
int lbt=msk&-msk;
msk=(msk+lbt)|(((msk^(msk+lbt))>>2)/lbt);
}
}
ans=0;for(i=0;i<n;i++){
if(c[i][0])continue; //如果当前节点与第一个节点的连线禁止则跳过。
ans+=f[top][i]; //求和
}
printf("%d\n",ans>>1);
}
fclose(stdin);fclose(stdout);
return 0;
}