暑假集训 || 状压DP

emm

位操作实现技巧:

获得第i位的数据:  if(!(data & (1<< i)))  则data的第 i 位为0,else 为 1

设置第i位为1,data=(data | (1<< i));

设置第i位为0,data=(data & (~(1<< i)))

将第i位取反,data=(data ^ (1<< i)

取出一个数的最后一个1 (lowbit):(data & (-data))

(二进制数从右往左最右为第0位)

 

Codeforces gym 101343 J

题意:给出一片田野,其中有0有1,1的地方可以选,挨着的两个格不能选,问有多少种选法

思路:把田野上可以选的状态用二进制表示,进行dp,因为每一行的情况只与它上一行有关

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int SZ = 40050;
const int INF = 1000000100;
const int mod = 100000000;
int tmp;
int f[15][SZ], st[SZ], a[15];
int m, n;
bool check(int x, int y)//判断田野情况为x时,y方案能不能放
{
    bool flag = true;
    for(int i = 0; i < n; i++)
        if((x&(1 << i)) == 0 && (y&(1 << i)) > 0) flag = false;//田野上这个点是0,而方案中这个点要放,不可以呀
    return flag;
}
void init()//初始化出所有可能存在的状态
{
    int tot = 0;
    for(int i = 0; i < (1<<n); i++)
        if(!(i & (i<<1))) st[++tot] = i;
}
int main()
{
    scanf("%d %d", &m, &n);
    for(int i = 1; i <= m; i++) a[i] = 0, f[i] = 0;
    for(int i = 1; i <= m; i++)
        for(int j = 1; j <= n; j++)
        {
            scanf("%d", &tmp);
            if(tmp) a[i] = a[i] | (1<<(n-j));//将这一位设为1
        }
    for(int i = 1; i <= tot; i++)
    {
        if(check(a[1], st[i])) f[1][i] = 1;
    }
    for(int i = 2; i <= m; i++)
        for(int j = 1; j <= tot; j++)//枚举第i行的状态
            if(check(a[i], st[j]))//第i行的j状态合法
                for(int k = 1; k <= tot; k++)//枚举第i-1行状态
                    if(check(a[i-1], st[k]) && !(st[k] & st[j]))
                        f[i][j] = (f[i][j] + f[i-1][k]) % mod;
    int ans = 0;
    for(int i = 1; i <= tot; i++)
        ans = (ans + f[m][i]) % mod;
    printf("%d\n", ans);
    return 0;
}
View Code

 

POJ 3254

题意:构造一个序列,包括给出的n个子序列,问这个序列的最短长度

思路:第一反应贪心 然鹅是枚举去做状压dp

f[i][j] 表示状态 i 中最后一个序列是 j 的长度,下次更新的时候在它后面接k

把k接在它后面 f[i|(1<<(k-1))][k] = min{f[i][j] + num[k] - arr[j][k]};

细节:预处理每两个数组可以互相覆盖的大小之前要处理一下完全包含的情况

然后。。1<<(i-1) QAQ窝数组下标是从1开始开的哇。。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int SZ = 60050;
const int INF = 1000000100;
const int mod = 100000000;
int num[22], a[22][111], arr[22][22];
int f[SZ][22], vis[22];
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &num[i]);
        for(int j = 1; j <= num[i]; j++)
            scanf("%d", &a[i][j]);
    }
    for(int i = 1; i <= n; i++)//除去被a[i]包含的串//1 2 3 4 5
    {
        if(vis[i]) continue;
        for(int j = 1; j <= n; j++)//看看a[j]有没有被a[i]包含//2 3 4
        {
            if(i == j || vis[j] || num[i] < num[j]) continue;
            for(int k = 1; k <= num[i]; k++)
            {
                if(k + num[j] - 1 > num[i]) break;
                bool same = true;
                for(int tj = 1; tj <= num[j]; tj++)
                    if(a[i][k+tj-1] != a[j][tj]) same = false;
                if(same) {vis[j] = 1; break;}
            }
        }
    }
    int idx = 0;
    for(int i = 1; i <= n; i++)
        if(!vis[i])
        {
            num[++idx] = num[i];
            for(int j = 1; j <= num[i]; j++)
                a[idx][j] = a[i][j];
        }
    n = idx;
    for(int i = 1; i <= n; i++)//把j接在i后面
        for(int j = 1; j <= n; j++)
        {
            if(i == j) continue;
            for(int len = 1; len <= num[i]; len++)
            {
                bool flag = true;
                for(int k = 1; k <= len; k++)
                    if(a[i][num[i]-len+k] != a[j][k]) flag = false;
                if(flag) arr[i][j] = len;
            }
        }
    memset(f, 63, sizeof(f));
    for(int i = 1; i <= n; i++)
        f[1<<(i-1)][i] = num[i];
    for(int i = 1; i < (1<<n); i++)
        for(int j = 1; j <= n; j++)
            for(int k = 1; k <= n; k++)//在j后放k
                if(i&(1<<(j-1)) || !i&(1<<(k-1)))
                    f[i|(1<<(k-1))][k] = min(f[i|(1<<(k-1))][k], f[i][j] + num[k] - arr[j][k]);
    int ans = INF;
    for(int i = 1; i <= n; i++)
        ans = min(ans, f[(1<<n)-1][i]);
    printf("%d\n", ans);
    return 0;
}
View Code

 

BZOJ 1087

题意:在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它一圈的八个地方(1 <=N <=9, 0 <= K <= N * N)

思路:看到这个数据范围就想到状压,每一行的情况都能用一个9位二进制数表示

f[i][j] 表示第 i 行放 j 情况的方法数,从f[i-1][k]转移而来,转移时判断 j 和 k 是否冲突

因为总共的数量有限制,所以再加一维表示到当前放了多少个

c数组表示这种情况下有多少个1

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int SZ = 1050;
const int INF = 1000000100;
const int mod = 100000000;
LL f[11][SZ][111], st[SZ], c[SZ];
int m, n;
//f[i][j][k] 第i行,状态为st[j],已经放了k个
bool check(int x, int y)
{
    bool flag = true;
    if(y&x || y&(x<<1) || y &(x>>1)) flag = false;
    return flag;
}

int main()
{
    scanf("%d %d", &n, &m);
    int tot = 0;
    for(int i = 0; i < (1<<n); i++)
        if(!(i & (i<<1)))
        {
            st[++tot] = i;
            for(int j = 0; j < n; j++)
                if(i&(1<<j)) c[tot]++;
        }
    for(int i = 1; i <= tot; i++)
        f[1][i][c[i]] = 1;
    for(int i = 2; i <= n; i++)
        for(int j = 1; j <= tot; j++)//枚举第i行的状态
            for(int k = 1; k <= tot; k++)//枚举第i-1行状态
            {
                if(check(st[j], st[k]))
                {
                    for(int t = c[j]; t <= m; t++)
                        f[i][j][t] += f[i-1][k][t-c[j]];
                }
            }
    LL ans = 0;
    for(int i = 1; i <= tot; i++)
        ans += f[n][i][m];
    printf("%lld\n", ans);
    return 0;
}
View Code

 

posted @ 2018-08-29 17:02  舒羽倾  阅读(131)  评论(0)    收藏  举报