Lineup the Dominoes(UCF Local Programming Contest 2016 I,状压 DP)

一.题目链接:

Lineup the Dominoes

二.题目大意:

T 组样例

每组有一个整数 n (n <= 16),表示有 n 个双面多米诺骨牌,每个多米诺骨牌正面有数字 s,反面有数字 t.

n 个多米诺骨牌可以排成一行当且仅当相邻的多米诺骨牌的相邻面上的数字相同(可以对任意多个多米诺骨牌正反面翻转).

问这 n 个多米诺骨牌排成一行有多少种方式(只有两种排列的多米诺骨牌顺序不同时,才为不同的排列).

三.分析:

dp[i][j][k] 表示 n 个骨牌已用的状态为 i,最后一个骨牌是 j,且最后一个骨牌 不翻转(0) / 翻转(1).

设计完状态后很容易写出状态转移方程,不过需要注意方案只与顺序不同,与是否翻转无关.

在最后的答案统计中还有一个小坑点:

很自然想到只需要枚举最后一个骨牌 i

if s[i] == t[i]:    ans += dp[(1<<n) - 1][i][0]

else                ans += dp[(1<<n) - 1][i][0] + dp[(1<<n) - 1][i][1].

即我一开始认为如果最后一个骨牌正反面的数字不同,则一定对应着不同的排列.

其实这样漏掉了一种情况.

下面是我的思路历程(不是证明,写得极其不严谨!)

假设当前序列为 (a1,a2) (a2,a3) (a3,a4) ... (an-1,an) (an,an+1) ,a2 != a3, a3 != a4, ..., an-1 != an, an != an +1.

翻转最后一个骨牌后序列为 (a1,a2) (a2,a3) (a3,a4) ... (an-2,an-1) (an-1,an) (an+1,an)

假设两个序列均合法且相异,但这与 (an-1,an) (an+1,an) 相邻明显矛盾.

那为了满足合法我们只能翻转 (an-1,an),那么序列变为 (a1,a2) (a2,a3) (a3,a4) ... (an-2,an-1) (an,an-1) (an+1,an)

其中 (an-2,an-1) (an,an-1) 相邻又产生矛盾,由此我们这样递归翻转下去,得到的序列为

(a2,a1) (a3,a2) (a4,a3) ... (an-1,an-2) (an,an-1) (an+1,an)

由于我们假设了这个序列与原序列均合法且相异,不难得出:a1 = a3 = a5 = ...; a2 = a4 = a6 = ...;

即这 n 个多米诺骨牌两两相同.

可推得:在最后一个骨牌正反面数字不同的情况下,如果 n 个骨牌两两相同,则骨牌翻转后仍对应同一种序列,否则一定对应着不同的序列.

综上我们需要特判所有骨牌相同的情况,答案为 n!

 四.代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int M = (int)1e5;
const int inf = 0x3f3f3f3f;
const ll mod = (ll)1e9 + 7;

int s[16][2];
ll fac[17];
ll dp[1<<16][16][2];

bool check(int n)
{
    for(int i = 1; i < n; ++i)  if(!(s[0][0] == s[i][0] && s[0][1] == s[i][1] || s[0][0] == s[i][1] && s[0][1] == s[i][0]))    return 0;
    return 1;
}

int main()
{
    fac[0] = 1; for(int i = 1; i <= 16; ++i)    fac[i] = fac[i - 1] * i % mod;
    int T; scanf("%d", &T);
    while(T--)
    {
        int n; scanf("%d", &n);
        for(int i = 0; i < n; ++i)  scanf("%d %d", &s[i][0], &s[i][1]);
        if(check(n))                {printf("%lld\n", fac[n]); continue;}
        memset(dp, 0, sizeof(dp));
        for(int i = 0; i < n; ++i)  dp[1<<i][i][0] = dp[1<<i][i][1] = 1;
        for(int i = 3; i < (1<<n); ++i)
        {
            for(int j = 0; j < n; ++j)
            {
                if(!(i & (1<<j)))   continue;
                int state = (i ^ (1<<j));
                for(int k = 0; k < n; ++k)
                {
                    if(!(state & (1<<k)))   continue;
                    if(s[k][0] == s[j][0])      dp[i][j][0] = (dp[i][j][0] + dp[state][k][1]) % mod;
                    else if(s[k][1] == s[j][0]) dp[i][j][0] = (dp[i][j][0] + dp[state][k][0]) % mod;
                    if(s[k][0] == s[j][1])      dp[i][j][1] = (dp[i][j][1] + dp[state][k][1]) % mod;
                    else if(s[k][1] == s[j][1]) dp[i][j][1] = (dp[i][j][1] + dp[state][k][0]) % mod;
                }
            }
        }
        ll cnt = 0;
        for(int i = 0; i < n; ++i)  {cnt = (cnt + dp[(1<<n) - 1][i][0]) % mod; if(s[i][0] != s[i][1])   cnt = (cnt + dp[(1<<n) - 1][i][1]) % mod;}
        printf("%lld\n", cnt);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/The___Flash/article/details/105167253