状态压缩DP
状态压缩DP通常用一段二进制序列表示一个状态,状态压缩DP有个非常显著的特点,问题一般都是最优化问题或者是计数问题,数据范围一般很小 n ≤ 20 n \leq 20 n≤20。
普通状压DP
这是典型的旅行商问题,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]=k∈S−{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;
}
这题使用了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;
}
注意,状态压缩动态规划是有限制的,状态不超过 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
经典的轮廓线问题。我们对每一行进行描述,用 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;
}
同样的轮廓线算法,只不过多了一个放置国王的数量的状态。
#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个位置的数量或者最优匹配。
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];
}
};
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];
}
};