状态压缩DP

状态压缩DP

状态压缩DP通常用一段二进制序列表示一个状态,状态压缩DP有个非常显著的特点,问题一般都是最优化问题或者是计数问题,数据范围一般很小 n ≤ 20 n \leq 20 n20

普通状压DP

P1433 吃奶酪

这是典型的旅行商问题,OI届未找到多项式时间的解法,因此只能暴力求解。

定义状态 d p [ S ] [ i ] dp[S][i] dp[S][i],为已经吃到的奶酪集合为 S S S,现在在第 i i i个奶酪的位置上,动态规划转移方程为:

d p [ S ] [ i ] = min ⁡ k ∈ S − { i } ( d p [ S ] [ i ] , d p [ S − { i } ] [ j ] + d i s j i ) dp[S][i] = \min_{k \in S-\{i\}}(dp[S][i],dp[S-\{i\}][j] + dis_{ji}) dp[S][i]=kS{i}min(dp[S][i],dp[S{i}][j]+disji)

已经很明显的状态转移,就是从剩下的集合中任意一个点转移过来。

#include <bits/stdc++.h>

using namespace std;

#define FR freopen("in.txt","r",stdin)

typedef long long ll;

double x[15],y[15];

inline double dis(int u,int v)
{
    return sqrt((x[u] - x[v]) * (x[u] - x[v]) + (y[u] - y[v]) * (y[u] - y[v]));
}

double dp[1 << 15][15];
int main()
{
    int n;
    scanf("%d",&n);

    for(int i = 0; i<n; i++)
    {
        scanf("%lf %lf",x + i,y + i);
    }

    for(int to = 0;to<n;to++)
    {
        dp[1 << to][to] = sqrt(x[to] * x[to] + y[to] * y[to]);
    }

    for(int S = 1; S <= (1 << n) - 1;S++)
    {
        for(int to = 0;to<n;to++)
        {
            if(S & (1 << to))
            {
                int fS = S - (1 << to);
                for(int from = 0;from < n;from++)
                {
                    if(fS & (1 << from))
                    {
                        if(dp[S][to] == 0) dp[S][to] = DBL_MAX;
                        dp[S][to] = min(dp[S][to],dp[fS][from] + dis(from,to));
                    }
                }
            }
        }
    }
    double ans = DBL_MAX;
    for(int ed = 0;ed<n;ed++)
    {
        ans = min(ans,dp[(1 << n) - 1][ed]);
    }

    printf("%.2lf",ans);
    return 0;
}

P1441 砝码称重

这题使用了std::bitset数据结构表示能够生成的集合。

将一个集合先位移表示加上这个砝码,然后再合并集合。

#include <bits/stdc++.h>

using namespace std;

#define FR freopen("in.txt","r",stdin)

typedef long long ll;

int arr[21];

bool online[21];
int n,m;

int ans = 0;

void solve()
{
    std::bitset<2010> bit(1);
    for(int i = 1;i<=n;i++)
    {
        if(online[i])
        {
            bit = bit | (bit << arr[i]);
        }
    }

    ans = max(ans,(int)bit.count() - 1);
}

void dfs(int del,int curr)
{
    if(del == 0)
    {
        solve();
    }else if(curr <= n)
    {
        online[curr] = false;
        dfs(del - 1,curr+1);
        online[curr] = true;
        dfs(del,curr+1);
    }
}


int main()
{
    cin >> n >> m;
    for(int i = 1;i<=n;i++)
    {
        cin >> arr[i];
        online[i] = true;
    }

    dfs(m,1);

    cout << ans;
    return 0;
}

LeetCode 1239

注意,状态压缩动态规划是有限制的,状态不超过 20 20 20次方。此题状态为 26 26 26,如果再状态压缩的话可能会超时,我们发现输入长度很少,我们可以选择预处理状态+DFS枚举子集。


class Solution
{
public:
    int mx = 0;
    void solve(int stats, vector<pair<int, int>> &vecstr, int idx, int si)
    {
        if (idx == vecstr.size())
        {
            mx = max(mx, si);
            return ;
        }

        if ((stats & vecstr[idx].first) == 0)
        {
            solve(stats | vecstr[idx].first, vecstr, idx + 1, si + vecstr[idx].second);
        }

        solve(stats, vecstr, idx + 1, si);
    }

    int maxLength(vector<string> &arr)
    {
        vector<pair<int, int>> vecstr;
        for (int i = 0; i < arr.size(); i++)
        {
            string &str = arr[i];
            int bit = 0;
            bool ok = true;
            int cnt = 0;
            for (int j = 0; j < str.size(); j++)
            {
                int b = str[j] - 'a';
                if (((1 << b) & bit) != 0)
                {
                    ok = false;
                    break;
                }
                else
                {
                    bit |= (1 << b);
                    cnt++;
                }
            }
            if (ok)
            {
                vecstr.push_back({bit, cnt});
            }
        }

        solve(0,vecstr,0,0);

        return mx;
    }
};


轮廓线状压DP

POJ2411 砖块铺设

经典的轮廓线问题。我们对每一行进行描述,用 0 0 0代表D, 1 1 1代表ULR,然后进行状态压缩,之后进行DP,设状态 d p [ r ] [ s ] dp[r][s] dp[r][s] r r r行的状态为 s s s的方案数。

#include <cstdio>
#include <algorithm>

#define FR freopen("in.txt", "r", stdin)

typedef long long ll;
using namespace std;

ll ans[12][12];

ll dp[13][1 << 11];

bool check(int curr, int prv, int n)
{
    bool cong = false;
    int odd = 0;
    for (int i = 0; i < n; i++)
    {
        int cbit = (curr >> i) & 1;
        int pbit = (prv >> i) & 1;

        if (pbit == 1 && cbit == 1)
        {
            odd ^= 1;
            cong = odd;
        }
        else if (pbit == 0 && cbit == 0)
        {
            return false;
        }
        else if (pbit == 1 && cbit == 0)
        {
            if (cong)
            {
                return false;
            }
        }
        else if (pbit == 0 && cbit == 1)
        {
            if (cong)
            {
                if (odd)
                {
                    return false;
                }
                else
                {
                    cong = false;
                }
            }
        }
    }

    if (cong && odd)
    {
        return false;
    }
    else
    {
        return true;
    }
}

ll solve(int m, int n)
{
    static int lasted = 0;
    int ed = 1 << n;
    dp[0][lasted] = 0;
    lasted = ed - 1;
    dp[0][ed - 1] = 1;
    for (int r = 1; r <= m + 1; r++)
    {
        for (int curr = 0; curr < ed; curr++)
        {
            ll sum = 0;
            for (int prv = 0; prv < ed; prv++)
            {
                if (check(curr, prv, n))
                {
                    sum += dp[r - 1][prv];
                }
            }
            dp[r][curr] = sum;
        }
    }

    return dp[m + 1][0];
}

int main()
{
    for (int h = 1; h <= 11; h++)
    {
        for (int w = h; w <= 11; w++)
        {
            if ((h & 1) && (w & 1))
            {
                ans[h][w] = ans[w][h] = 0;
            }
            else
                ans[h][w] = ans[w][h] = solve(w, h);
        }
    }
    int m, n;
    while (1)
    {
        scanf("%d %d", &m, &n);
        if (m == 0 && n == 0)
        {
            break;
        }
        printf("%lld\n", ans[m][n]);
    }
    return 0;
}

P1896

同样的轮廓线算法,只不过多了一个放置国王的数量的状态。

#include <bits/stdc++.h>

#define FR freopen("in.txt", "r", stdin)

using namespace std;

typedef long long ll;

ll dp[10][2048][85];
int n, k;

bool checkOne(uint32_t stat)
{
    return (((stat >> 1) & stat) == 0) && (((stat << 1) & stat) == 0);
}

bool checkTwo(uint32_t stat1, uint32_t stat2)
{
    return ((stat1 & stat2) == 0) && (((stat1 << 1) & stat2) == 0) && (((stat1 >> 1) & stat2) == 0);
}

int main()
{
    scanf("%d %d", &n, &k);
    uint32_t ed = 1 << n;
    for (uint32_t stat = 0; stat < ed; stat++)
    {
        int popc = __builtin_popcount(stat);
        if (checkOne(stat) && popc <= k)
            dp[1][stat][popc] = 1;
    }
    for (int r = 2; r <= n; r++)
        for (uint32_t stat = 0; stat < ed; stat++)
        {
            int popc = __builtin_popcount(stat);
            if (!checkOne(stat) || popc > k)
                continue;
            for (int ki = popc; ki <= k; ki++)
            {
                for (uint32_t prv = 0; prv < ed; prv++)
                {
                    int popcc = __builtin_popcount(prv);
                    if (!checkOne(prv) || popcc > ki - popc)
                        continue;
                    if (checkTwo(stat, prv))
                        dp[r][stat][ki] += dp[r - 1][prv][ki - popc];
                }
            }
        }
    ll ans = 0;
    for (uint32_t stat = 0; stat < ed; stat++)
    {
        ans += dp[n][stat][k];
    }

    printf("%lld",ans);
    return 0;
}

二分匹配状压DP

该问题的模型是,给定 n n n个不同的位置和 n n n个不同的物品,每一个物品都可以匹配一个位置,或求最佳匹配,或求匹配数量。

我们定义 f [ m a s k ] f[mask] f[mask]为将 m a s k mask mask 1 1 1的位置的物品已经匹配到前 n n n个位置的数量或者最优匹配。

LeetCode 526

class Solution
{
  public:
    int countArrangement(int n)
    {
        int ed = 1 << n;
        vector<int> dp(ed);
        dp[0] = 1;
        for (int stat = 1; stat < ed; stat++)
        {
            int loc = __builtin_popcount(stat);
            for (int i = 1; i <= n; i++)
            {
                if ((stat >> (i - 1)) & 1)
                {
                    int prv = stat - (1 << (i - 1));
                    if (i % loc == 0 || loc % i == 0)
                    {
                        dp[stat] += dp[prv];
                    }
                }
            }
        }

        return dp[ed - 1];
    }
};

LeetCode 1947


class Solution
{
  public:
    int maxCompatibilitySum(vector<vector<int>> &students, vector<vector<int>> &mentors)
    {
        int n = students[0].size();
        int ed = 1 << students.size();
        vector<int> dp(ed);
        for (int stat = 1; stat < ed; stat++)
        {
            int teacher = __builtin_popcount(stat) - 1;
            for (int i = 0; i < students.size(); i++)
            {
                if ((stat >> i) & 1)
                {
                    int prv = stat - (1 << i);
                    int score = 0;
                    for (int j = 0; j < n; j++)
                    {
                        if (students[i][j] == mentors[teacher][j]) score++;
                    }
                    dp[stat] = max(dp[stat], dp[prv] + score);
                }
            }
        }
        return dp[ed - 1];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值