title: DP状态设计方法
tags: []
categories: [OI, 算法专题]
本节的DP,主要用于讲解DP状态的设计,题目大多是状压DP和高维DP。包含一个数位DP。
状压DP的设计主要是以下两种:状压权值(从小到大),状压位置(从左到右)。需要根据题目性质具体问题具体设计选择。
P10561 ICPC 2024 Xi'an I Smart Quality Inspector
考点
状压DP
题意
- 有 \(N\) 个位置,其中 \(K\) 个位置的值为 \(1, 2, \ldots, K\),其余 \(N-K\) 个位置的值为 \(0\)。
- 给定 \(M\) 个区间 \([l_i, r_i]\),求 \(\sum_{i=1}^M \max_{j=l_i}^{r_i} A_j\)。
- 可以重新排列序列 \(A\),求上述和的最小值。
数据范围:\(1 \le K \le N \le 20\),\(1 \le M \le 10^5\)。
思路
看数据范围容易想到使用状压DP。
在大多数题目中,max是不好处理的。因为这个东西对答案造成的贡献不好统计。在本题中,我们考虑如何使一个区间的贡献确定。
状压DP大体上分为两种:状压位置(从左到右),状压权值(从小到大)。本题为了能达到要求,要从大到小状压。这样,单次区间的贡献能够确定。区间数量是未被选中的包含当前位置的区间数量。这个可以用二维前缀和解决。
我当时想这个二维前缀和是不是还要容斥一下啊,实际上不用。看代码易知。
代码
#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 21;
int n,k,m;
int mp[N][N];
int p[N][N];
int dp[(1 << N)];
signed main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> n >> k >> m;
f(i,1,m){
int l,r; cin >> l >> r;
mp[l][r]++;
}
f(i,1,n){
f(j,1,n){
p[i][j] = p[i-1][j] + p[i][j-1] - p[i-1][j-1] + mp[i][j];
// cout << p[i][j] <<" ";
}
// cout << "\n";
}
memset(dp,0x3f,sizeof dp);
dp[0] = 0;
f(st,0,(1 << n) - 1){
int i = __builtin_popcount(st) + 1;
if (i > k) continue;
f(j,1,n){
if (st >> (j - 1) & 1){
continue;
}
int l=j,r=j;
for (l = j;l>=1;l--){
if (st >> (l - 1) & 1) break;
}
for (r = j;r<=n;r++){
if (st >> (r-1) & 1) break;
}
l++;r--;
// if (st == 2 && (st | (1 << (j-1))) == 3){
// D(l);D(r);D(j);D((st | (1 << (j-1))));
// Dl(p[j][r] - p[j][j-1] - p[l-1][r] + p[l-1][j-1]);
// }
dp[st | (1 << (j-1))] = min(dp[st | (1 << (j-1))],dp[st] + (p[j][r] - p[j][j-1] - p[l-1][r] + p[l-1][j-1]) * (k - i + 1));
}
}
// Dl(dp[1]);
// Dl(dp[2]);
// Dl(dp[4]);
// Dl(dp[8]);
// Dl(dp[3]);
cout << dp[(1 << n) - 1];
return 0;
}
P4067 SDOI2016 储能表
考点
数位DP
题意
- 有一个 \(n\) 行 \(m\) 列的表格(行列从 \(0\) 编号),格子 \((i, j)\) 初始能量为 \(i \oplus j\)。
- 经过 \(k\) 个时间单位后,每个格子的能量减少 \(k\) 但不低于 \(0\),即剩余能量为 \(\max((i \oplus j) - k, 0)\)。
- 求 \(k\) 个时间单位后整个表格的总能量,对 \(p\) 取模。
- 多组数据。
数据范围:\(1 \le T \le 5000\),\(1 \le n, m, k \le 10^{18}\),\(1 \le p \le 10^9\)。
思路
数位DP。
由于是异或,按位选择。设计状态为当前位,是否被n限制,是否被m限制,是否被k限制。
把答案拆开,发现需要知道大于k的位置,计为cnt。ans = 异或结果大于k的和 - cnt * k。
到这儿思路就顺了,但是代码我调了很久。
-1 使用1ll左移而不是1
-2 括号加的不对/没加
-3 异或和忘统计了
-4 统计答案在dfs里,而不是外面。实际上,数位DP大多是在外面统计的。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 71;
int t;
int n, m, k, p;
int bitN[N], bitM[N], bitK[N], cntN, cntM, cntK;
pii vis[N][2][2][2];
int ans;
void init(int n, int m, int k) {
memset(bitN, 0, sizeof bitN);
memset(bitM, 0, sizeof bitM);
memset(bitK, 0, sizeof bitK);
memset(vis, 0, sizeof vis);
cntN = cntM = cntK = ans = 0;
while (n) {
bitN[cntN++] = n % 2;
n /= 2;
}
while (m) {
bitM[cntM++] = m % 2;
m /= 2;
}
while (k) {
bitK[cntK++] = k % 2;
k /= 2;
}
cntN --;cntM--;cntK--;
}
pii bitFill(int i, int bn, int bm, bool limitN, bool limitM, bool limitK);
pii dfs(int i, bool limitN, bool limitM, bool limitK) {
// Dl(i);
if (i == -1) {
return {1,0};
}
if (vis[i][limitN][limitM][limitK].first) {
// ans = (ans + vis[i][limitN][limitM][limitK].second) % p;
return vis[i][limitN][limitM][limitK];
}
int res = 0,res2 = 0;
pii tmp = bitFill(i, 0, 0, limitN, limitM, limitK);
res = (tmp.first + res) % p;res2 = (tmp.second + res2) % p;
tmp = bitFill(i, 0, 1, limitN, limitM, limitK);
res = (tmp.first + res) % p;res2 = (tmp.second + res2) % p;
tmp = bitFill(i, 1, 0, limitN, limitM, limitK);
res = (tmp.first + res) % p;res2 = (tmp.second + res2) % p;
tmp = bitFill(i, 1, 1, limitN, limitM, limitK);
res = (tmp.first + res) % p;res2 = (tmp.second + res2) % p;
// if (i > 0){
// Dl(res - tmp);
// }
vis[i][limitN][limitM][limitK].first = res % p;
vis[i][limitN][limitM][limitK].second = res2 % p;
// ans = (ans - res * k % p + p) % p;
// ans = (ans + (1ll << i) % p * res % p) % p;
// D(i);Dl(res);
return vis[i][limitN][limitM][limitK];
}
pii bitFill(int i, int bn, int bm, bool limitN, bool limitM, bool limitK) {
if (limitN && bitN[i] < bn) return {0,0};
if (limitM && bitM[i] < bm) return {0,0};
if (limitK && bitK[i] > (bn ^ bm)) return {0,0};
pii tmp = dfs(i - 1, (bitN[i] == bn && limitN), (bitM[i] == bm && limitM), (limitK && (bn ^ bm == bitK[i])));
// if (i == 0) Dl((1ll << i) *(bn ^ bm) % p * tmp % p);
// ans = (ans + tmp.second + (1ll << i) * (bn ^ bm) % p * tmp.first % p) % p;
// Dl(tmp.second);
// Dl((1ll << i) * (bn ^ bm) % p * tmp.first % p);
return {tmp.first,tmp.second + (1ll << i) * (bn ^ bm) % p * tmp.first % p};
}
void solve() {
cin >> n >> m >> k >> p;
n--;m--;
init(n, m, k);
// Dl(bitN[1]);
pii tmp = dfs(62, 1, 1, 1);
// Dl(cnt);
ans = (ans - tmp.first * (k % p) % p + p) % p;
ans = (ans + tmp.second % p) % p;
cout << ans << "\n";
}
signed main() {
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin >> t;
while (t--) {
solve();
}
return 0;
}
浙公网安备 33010602011771号