【代碼】 狀態壓縮 動態規劃 周偉論文 代碼補全計劃

嗯,所有需要的代碼(包括全部7個例題及註釋中的實現)都有,還附贈了兩三個練習。希望能對需要的人有幫助(只要不是淹沒到百度里根本搜不出來就好了)


前言

先確認我們說的是同一個東西
論文開頭爲
論文
網上應該都下得到吧,就不放鏈接了
有論文的話我就不贅述原文了,大概說明原題是什麼,再放個人代碼
其實代碼很多是%了各位dalao碼出來的,所以代碼中我也會說一些我發現的好的地方
csdn的代碼片有點反人類啊,實在不行的話,麻煩各位自行用編輯器自動縮進了
若能在oj上找到的我也儘量直接貼上oj題號
還會有很少的練習題

某日update
【例7】完成!對於進制的理解可以更深一些



正文

【引例】

題目描述

在 n*n(n≤20)的方格棋盤上放置 n 個車(可以攻擊所在行、列),求使它們不能互相攻擊的方案總數。

代碼

#include <cstdio>
#include <cstring>

const int maxn = 10;
int f[1<<maxn + 10] = {0}, lim;
inline int lowbit(int x){return -x&x;}
int main (){
    int n; scanf ("%d", &n);
    f[0] = 1, lim = (1<<n);
    for (int s = 1, lk; s < lim; ++s){
        for (int k = s; k > 0; k -= lk){
            f[s] += f[s ^ (lk = lowbit(k))]; //這種打法應該很有效率啦
        }
    }
    printf ("%d\n", f[lim - 1]); 

    return 0;
}

【例1]

題目描述

在 n*n(n≤20)的方格棋盤上放置 n 個車,某些格子不能放,求使它們不能互相攻擊的方案總數。

代碼

#include <cstdio>
#include <cstring>

const int maxn = 10;
int f[1<<maxn + 10], out[maxn], lim;
inline int lowbit(int x){return -x&x;}
int main (){
    freopen ("1.in", "r", stdin);
    int n, m; scanf ("%d%d", &n, &m);
    for (int i = 1, x, y; i <= m; ++i){
        scanf ("%d%d", &x, &y);
        out[x] |= 1 << --y; //這行在網上看到的有些代碼打得奇奇怪怪的...
    }
    f[0] = 1, lim = (1 << n) - 1;
    for (int s = 1, st, h, lk; s < lim; ++s){
        st = s, h = 0;
        while (st) st -= lowbit(st), ++h;
        for (int j = s ^ out[h]; j > 0; j -= lowbit(j)){
           f[s] += f[s ^ (lk = lowbit(j))];
        }
    }
    printf ("%d\n", f[lim - 1]);       

    return 0;
}

poj1321好像是例1的一點點拓展,這個練習是臨時加的

棋盤問題 POJ1321(練習)

題目描述

在一個給定形狀的棋盤(形狀可能是不規則的)上面擺放棋子,棋子沒有區別。要求擺放時任意的兩個棋子不能放在棋盤中的同一行或者同一列,請編程求解對於給定形狀和大小的棋盤,擺放k個棋子的所有可行的擺放方案C。
具體輸入輸出什麼的自己去看就好了,中文題

代碼

#include <cstdio>
#include <cstring>

const int maxn = 10;
char tmps[maxn];
int lim, f[maxn][1<<maxn], c[1<<maxn], rs[75], rst, out[maxn];
inline int Cal(int x){
    int ans = 0;
    while (x){if(x & 1) ++ans; x >>= 1;}
    return ans;
}
inline int lowbit(int x){return -x & x;}
int dp(int rti, int rts){
    if (f[rti][rts]) return f[rti][rts];
    if (!rti) return 0;
    for (int s = out[rti] ^ rts; s > 0; s -= lowbit(s))
        f[rti][rts] += dp(rti - 1, lowbit(s) ^ rts);
    return f[rti][rts] += dp(rti - 1, rts);
}
int main (){
    freopen ("p1321.in", "r", stdin);
    for (int s = 0; s < (1 << maxn); ++s) c[s] = Cal(s);
    int n, k, ans;
    while (scanf ("%d%d", &n, &k), n > 0){
        rst = ans = 0;
        memset(out, 0, sizeof out), memset(f, 0, sizeof f);
        for (int i = 1; i <= n; ++i){
            scanf ("%s", tmps);
            for (int j = 0; j < n; ++j)
                if (tmps[j] == '.')
                    out[i] |= 1 << j;
        }lim = 1 << n;
        for (int s = 0; s < lim; ++s)
            if (c[s] == k) rs[rst++] = s;
        f[0][0] = 1;
        for (int i = 0; i < rst; ++i) ans += dp(n, rs[i]);
            printf ("%d\n", ans);
    }

    return 0;
}

【例2】(僞)

題目描述

爲什麼說是僞呢,因爲我強行改題目,就求方案數就好吧
給出一個 n*m 的棋盤(n、m≤80,n*m≤80),要在棋盤上放 k(k≤20)個棋子,使得任意兩個棋子不相鄰。求方案數

代碼

關於註釋中的遞推程序

#include <cstdio>

const int maxn = 1010;
int g[maxn];
int main (){
    int m, d; scanf ("%d%d", &m, &d);
    for (int i = 1; i <= d; ++i) g[i] = 1;
    for (int j = d + 1; j <= m + d + 1; ++j) g[j] = g[j - d - 1] + g[j - 1];
    printf ("%d", g[m + d + 1]);

    return 0;
}

題目實現

#include <cstdio>

typedef long long ll;
const int maxnum = 150, maxn = 10, maxk = 25;
int num = 0, s[maxnum], c[maxnum];
ll f[maxn][maxnum][maxk];
template <typename T>
inline void Swap(T &a, T &b){int t = a; a = b, b = t;}
inline int cal(int x){
    int ans = 0;
    while (x){if (x & 1) ++ans; x>>=1;}
    return ans;
}
int main (){
    freopen ("2.in", "r", stdin);
    freopen ("2.out", "w", stdout);
    int n, m, k; scanf ("%d%d%d", &n, &m, &k);
    if (m > n)Swap(n, m);

    int collim = 1 << m;
    for (int i = 0; i < collim; ++i)
    if (!(i & (i << 1))) s[++num] = i, c[num] = cal(i);
//, f[1][num][c[num] = cal(i)] = 1;
    f[0][1][0] = 1;
    for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= num; ++j){
        for (int pn = c[j]; pn <= k; ++pn)
        for (int p = 1; p <= num; ++p){
            if (!(s[j] & s[p]))
            f[i][j][pn] += f[i - 1][p][pn - c[j]];
        }
    }
    ll ans = 0;
    for (int i = 1; i <= num; ++i) ans += f[n][i][k];
    printf ("%lld", ans);

    return 0;
}

【例3】

題目描述

在 n*n(n≤10)的棋盤上放 k 個國王(可攻擊相鄰的 8 個格子),求使它們無法互相攻擊的方案數。

代碼

#include <cstdio>

typedef long long ll;
const int maxn = 13, maxnum = 150, maxk = 25;
ll f[maxn][maxnum][maxk];
int s[maxnum], c[maxnum], num = 0;
inline int cal(int x){
    int ans = 0;
    while (x){if (x & 1) ++ans; x >>= 1;}
    return ans;
}
int main (){
    freopen ("3.in", "r", stdin);
    //freopen ("3.out", "w", stdout);
    int n, k; scanf ("%d%d", &n, &k);
    int lim = 1 << n;
    for (int i = 0; i < lim; ++i)
    if (!(i & (i << 1))) s[++num] = i, c[num] = cal(i);
    f[0][1][0] = 1;
    for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= num; ++j)
        for (int pn = c[j]; pn <= k; ++pn)
        for (int p = 1; p <= num; ++p)
            if (!(s[j] & s[p]) && !((s[j] << 1) & s[p]) && !((s[j] >> 1) & s[p]))
            f[i][j][pn] += f[i - 1][p][pn - c[j]];
    ll ans = 0;
    for (int i = 1; i <= num; ++i) ans += f[n][i][k];
    printf ("%lld", ans);

    return 0;
}

【例4】(POJ1185)

題目描述

給出一個 n*m(n≤100,m≤10)的棋盤,一些格子不能放置棋子。求最多能在棋盤上放置多少個棋子 ,
使得每一行每一列的任兩個棋子間至少有兩個空格

代碼

#include <cstdio>

const int maxn = 105, maxm = 12, maxs = 65;
char tmps[maxm];
int out[maxn], f[maxn][maxs][maxs], s[maxs], c[maxs], num = 0, lim;
inline int Cal(int x){
    int ans = 0;
    while (x){if (x & 1) ++ans; x >>= 1;}
    return ans;
}
int main (){
    int n, m; scanf ("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i){
    scanf ("%s", tmps);
    for (int j = 0; j < m; ++j)
        if (tmps[j] == 'H') out[i] |= 1 << j;
    }
    lim = 1 << m;
    for (int i = 0, ci; i < lim; ++i)
    if (!(i & (i << 2)) && !(i & (i << 1))) s[++num] = i, c[num] = Cal(i);
    for (int i = 1; i <= num; ++i)
    if (!(s[i] & out[1])){
        f[1][i][1] = c[i];
        for (int j = 1; j <= num; ++j)
        if (!(s[j] & s[i]) && !(s[j] & out[2]))
            f[2][j][i] = c[i] + c[j];
    }
    for (int i = 3, mx; i <= n; ++i)
    for (int j = 1; j <= num; ++j)
        if (!(s[j] & out[i]))
        for (int k = 1; k <= num; ++k)
            if (!(s[j] & s[k]) && !(s[k] & out[i - 1])){
            mx = 0;
            for (int l = 1; l <= num; ++l)
                if (!(s[l] & out[i - 2]))
                if (!(s[j] & s[l]) && !(s[k] & s[l]) && f[i - 1][k][l] > mx)
                    mx = f[i - 1][k][l];
            f[i][j][k] = mx + c[j];
            }
    int ans = 0;
    for (int j = 1; j <= num; ++j)
    if (!(s[j] & out[n]))
        for (int k = 1; k <= num; ++k)
        if (!(s[j] & s[k]) && !(s[k] & out[n - 1]) && f[n][j][k] > ans)
            ans = f[n][j][k];
    printf ("%d", ans);

    return 0;
}

【騎士】(練習)

強行夾在中間233

題目描述

騎士 (knight.pas/c/cpp )

國際象棋中騎士的移動規則和中國象棋中的馬是類似的,它先沿着一個方向移動兩格,
再沿着與剛纔移動方向垂直的方向移動一格。路徑上的棋子並不會影響騎士的移動,但是如
果一個騎士走到了一個放有棋子的格子,它就會攻擊那個棋子。現在有一個 n*n 的棋盤,有
k 個騎士需要被擺到棋盤上去。那麼使所有騎士互不攻擊的擺放方式一共有多少種呢?

輸入數據

一行:兩個整數,n,k

輸出數據

一行:一個整數,爲擺放的方式數

樣例
輸入:knight.in

3 2

輸出:knight.out

28

輸入:knight.in

4 4

輸出:knight.out

412

數據範圍 ,分值 與時間限制

第一次用這個表格

數據編號 數據範圍 時間限制 單個測試點分值 總分值
1~15 1≤n≤4 1.0s 3 45
16~20 5≤n≤6 1.0s 5 25
21~25 7≤n≤8 5.0s 6 30

對於所有的數據,0<=k<=n
保證答案不超過 double 的精度。

代碼

#include <cstdio>
#include <cstring>

typedef long long ll;
const int maxn = 9, maxm = 260;//maxm is cal_ed
ll f[2][1<<maxn][1<<maxn][maxn];
int cal[1<<maxn], nx[1<<maxn][maxm], num[1<<maxn];
inline int Cal(int x){
    if (cal[x]) return cal[x]; int tx = x, ans;
    for (ans = 0; x; ){ if (x & 1) ++ans; x >>= 1;}
    return cal[tx] = ans;
}
int main (){
    freopen ("knight.in", "r", stdin);
    freopen ("knight.out", "w", stdout);

    int n, pk, lim; scanf ("%d%d", &n, &pk);
    if (n == 1){printf ("%d", pk <= 1 ? 1 : 0); return 0;}
    lim = 1 << n;
    for (int i = 0; i < lim; ++i){
    f[0][i][0][Cal(i)] = 1;
    for (int j = 0; j < lim; ++j)
        if (!((i << 2) & j) && !((i >> 2) & j)){
        f[1][j][i][Cal(j) + Cal(i)] = 1;
        nx[i][++num[i]] = j;
        }
    }

    for (int i = 3, k, l, rt; i <= n; ++i){
    rt = (i ^ 1) & 1;
    memset(f[rt], 0, sizeof f[rt]);
    for (int j = 0; j < lim; ++j)
        for (int pn = cal[j]; pn <= pk; ++pn)
        for (int nk = 1; nk <= num[j]; ++nk){
            k = nx[j][nk];
            for (int nl = 1; nl <= num[k]; ++nl){
            l = nx[k][nl];          
            if (!((l << 1) & j) && !((l >> 1) & j)){
                f[rt][j][k][pn] += f[rt ^ 1][k][l][pn - cal[j]];

            }
            }
        }
    memset(f[rt^1], 0, sizeof f[rt^1]);
    }
    ll ans = 0;
    for (int j = 0, k; j < lim; ++j)
    for (int nk = 1; nk <= num[j]; ++nk){
        k = nx[j][nk];
        ans += f[(n ^ 1) & 1][j][k][pk];
    }
    printf ("%lld", ans);

    return 0;
}

【例5】

題目描述

給出 n*m (1≤n、m≤11)的方格棋盤,用 1*2 的長方形骨牌不重疊地覆蓋這個棋盤,求覆蓋滿的方案
數。

代碼

其實並沒有完全按論文裏打,是參考dalaoKB代碼,更爲清晰明瞭

#include <cstdio>

typedef long long ll;
const int maxn = 12;
ll f[maxn][1 << 11];
int n, m;
template <typename T>
void Swap (T &a, T &b){T t = a; a = b, b = t;}
void dp (int li, int co, int rt, int la){//line, column, root, last
    if (co > m) return;
    if (co == m){ f[li][rt] += f[li - 1][la]; return ;}
    dp (li, co + 1, rt << 1, (la << 1) | 1);
    dp (li, co + 1, (rt << 1) | 1, la << 1);
    dp (li, co + 2, (rt << 2) | 3, (la << 2) | 3);
} 
int main (){
    freopen ("5.in", "r", stdin);
    freopen ("5.out", "w", stdout);

    int lim; scanf ("%d%d", &n, &m);
    if (m > n) Swap(n, m);
    lim = 1 << m;
    f[0][lim - 1] = 1;
    for (int i = 1; i <= n; ++i)
    dp(i, 0, 0, 0);
    printf ("%lld", f[n][lim - 1]);

    return 0;
}

【例6】

題目描述

給出 n*m (1≤n、m≤9)的方格棋盤,用 1*2 的矩形的骨牌和 L 形的(2*2 的去掉一個角)骨牌不重疊地
覆蓋,求覆蓋滿的方案數。

代碼

%%%KB%%%

#include <cstdio>

typedef long long ll;
const int maxn = 10;
ll f[maxn][1 << 9];
int n, m;
template <typename T>
void Swap (T &a, T &b){T t = a; a = b, b = t;}
void dp (int li, int co, int rt, bool fr, int la, bool fl){
    //line, column, root, fluence of root, last, fluence of last
    if (co == m){
        if (!fr && !fl) f[li][rt] += f[li - 1][la];
        return ;
    }
    dp (li, co + 1, rt << 1 | fr, 0, la << 1 | (fl ^ 1), 0); //no putting
    if (!fr && !fl){
        dp (li, co + 1, rt << 1 | 1, 0, la << 1, 0); //10/10
        dp (li, co + 1, rt << 1 | 1, 1, la << 1, 0); //10/11
        dp (li, co + 1, rt << 1 | 1, 0, la << 1, 1); //11/10
    }
    if (!fr){
        dp (li, co + 1, rt << 1 | 1, 1, la << 1 | (fl ^ 1), 0); //00/11
        dp (li, co + 1, rt << 1 | 1, 1, la << 1 | (fl ^ 1), 1); //01/11
    }
    if (!fl) dp (li, co + 1, rt << 1 | fr, 1, la << 1, 1); //11/01
} 
int main (){
    freopen ("6.in", "r", stdin);
    freopen ("6.out", "w", stdout);

    int lim; scanf ("%d%d", &n, &m);
    if (m > n) Swap(n, m); lim = 1 << m;
    f[0][lim - 1] = 1;
    for (int i = 1; i <= n; ++i) dp(i, 0, 0, 0, 0, 0);
    printf ("%lld", f[n][lim - 1]);

    return 0;
}

【例7】(COGS 1520)

題目描述

給出 n*m(n,m≤10)的方格棋盤,用 1*r 的長方形骨牌不重疊地覆蓋這個棋盤,求覆蓋滿的方案數

代碼

感謝COGS中野生神犇mikumikumi分享的代碼,基本框架都是他/她的,自己打了一遍,稍加優化
附神犇註釋

#include <cstdio>
#include <cstring>

typedef long long ll;
const int maxn = 11, maxr = 1e7;
ll f[2][maxr] = {0};//5 ^ 10 -> maxr
ll powr[maxn] = {1}, lim;//r進制狀態壓縮
int n, m, r;
template <typename T>
void Swap (T &a, T &b){T t = a; a = b, b = t;}
void dp(int li, int p, int s1, int s2){//s1是當前行的狀態,s2是上一行的
    if (p > m) return ;
    if (p == m){f[li][s1] += f[li ^ 1][s2]; return ;}
    for (int i = 0; i < r - 1; ++i) dp(li, p + 1, s1 * r + i, s2 * r + i + 1);
    dp(li, p + 1, s1 * r + r - 1, s2 * r);
    dp(li, p + r, s1 * powr[r] + powr[r] - 1, s2 * powr[r] + powr[r] - 1);
}
int main (){
    freopen ("examseven.in", "r", stdin);
    freopen ("examseven.out", "w", stdout);

    scanf ("%d%d%d", &r, &n, &m); 
    if (m % r && n % r){printf ("0"); return 0;}
    if (m > n) Swap(n, m);
    for (int i = 1; i <= m; ++i) powr[i] = powr[i - 1] * r;
    f[0][(lim = powr[m]) - 1] = 1;
    for (int i = 1; i <= n; ++i){
        dp(i & 1, 0, 0, 0);
        memset(f[(i & 1) ^ 1], 0, sizeof (f[0]));
    }
    printf ("%lld", f[n & 1][lim - 1]);

    return 0;
}

後記

這篇論文其實2016年11月就有計劃全部打完的
這裏寫圖片描述
,但一直都拖拖拖拖拖拖拖於是我就laji了 好的我知道這梗太冷…
代碼完成過程確實發現自己很多不足
以後要有類似這種有必要學習的,一定要當時就決心代碼實現
再貼下關於這個的sum吧一堆XX錯誤
1
沒有直接枚舉s^out
1 0意義不清
用s錯用j
2
一直錯用s[j] 爲 j
poj1321
rti爲零時
(~ &) == ^ ?
4
答案求最大卻求和
比較大小後賦的又不是所比較的值
5
floor第三次打還把方程弄錯,其他倒是一點沒錯了
6
永遠都不記得當前放的會影響到上一行,上一行的當前列不要放
7
lim 寫成 powr[n]
陷入幾乎抄代碼樣例也不過模式…
用兩個gdb一行行查,查出來是dp裏for中dp參數li寫成i

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章