Dynamic Programming
状态压缩DP
质数
题意 : 有 \(n\) 个数,问从中最多选几个数,使选出的数两两互质,$ n \le 1000 ,\ a_i\le1000 $ .
- 小于 32 的质数有 11 个。
- \(a_i\) 中大于 32 的质因数最多有一个 (32*32>1000)。
- 因此,最大质因数小于 32 的数直接转移,大于 32 的数以其最大质因数分组转移,每组只能选一个,由 2. 知各组数无交集。
- 时间复杂度 $ O(2^{11}\cdot n)$ 。
- 和它十分相似的题 : [NOI2019] 寿司晚宴。
CF453B Little Pony and Harmony Chest
题意 : 给定长度为 n 的序列 \(a\), 要求构造长为 n 的序列 \(b\), 满足 \(b\) 中所有元素互质,且 $ \sum{|a_i-b_i|}$ 最小,输出方案。 $ 1\le n \le 100, 1 \le a_i \le 30$ 。
- 首先 1 和所有数互质。因此对于任一个 \(i\),\(|a_i-b_i| \le |a_i-1|\) 否则令 \(b_i\) 等于 1 更优, 又因为 \(a_i \le 30\),因此 \(b_i<59\)。
- 这样问题就便简单了,58 以内的质数只有 16 个,可以状压。
- 以 \(f[i][S]\) 为考虑到 \(a\) 的第 i 位,已经用到的质数状态为 s 的最小值。
- 然后转移中顺便记录最优转移点,最后通过其还原 \(b\) 数组,就好了。
- 时间复杂度 \(O(n \cdot 2^{16} \cdot 58)\)。
SRM713 DFSCount
题意 : 给定一个 n 个点的无向图,求不同 dfs 序个数,\(n \le 14\) 。
- 首先看数据范围想到状压。
- 考虑设计状态,\(f[i][s]\) 表示以 i 为根,当前子图态为 s 的方案数(第 x 个点在子树内则 s 第x位为 1,否则为 0)。
- 考虑先枚举点作为整棵树的根,设为 x ,然后枚举和 x 相连的连通块,设为 T1, T2, T3 。(将 x 去掉后图会分为若干连通块)(类似点分治)。
- 考虑 T1 ,设 T1 状态为 s, 设 T1 中与 x 有连边的点为 y1, y2,y3, 则 T1 的方案数为 \(f[y1][s]+f[y2][s]+f[y3][s] = Sum_{T1}\)。
- 因此 x 的方案数为 \((Sum_{T1}+Sum_{T2}+Sum_{T3}) \cdot 3!\) (需要考虑 T1,T2,T3 的枚举顺序,因此乘组合数)。
- 于是递归到一点时,每次求分为几个连通块,按照 3. 4. 5. 的步骤就好。
最小斯坦纳树
题意 : 给你 n 个点 m 条边的带边权无向图,有 k 个关键点,求将 k 个点连通的最小边权和。
\(1 \le n \le 100,1 \le m \le 500,1 \le k \le 10,1 \le w_i \le 10^6\)。
- 答案一定为一棵树,若存在环,删去环上任意一条边,边权变小。
- 设 \(f[i][s]\) 为以 i 为根,联通,含有的关键点状态为 s(状压,有 i 则第 i 位为 1)的最小边权和。
- 对于 \(f[i][s]\) 有两种转移方式 :
\(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f[j][s]+w[j][i] \to f[i][s]\)
\(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f[i][t]+f[i][s-t] \to f[i][s]\) - 第二种转移,直接枚举子集,复杂度 \(O(n\cdot 3^k)\), 第一种考虑类似最短路,对于每个 s 跑一遍 dijkstra 即可,复杂度 \(O(m\log m \cdot 2^k)\)。
- 典型例题,[JLOI2015]管道连接,求最小斯坦纳森林,在斯坦纳树的基础上稍稍改动即可。
代码 :
const int N=105,M=1010,K=(1<<10)+3;
int n, m, k, f[N][K], p[12], st[N];
int bg[N],nx[M],to[M],v[M],idx;
struct Node {
int x, val;
bool operator<(const Node& T) const{
return val > T.val;
}
};
priority_queue<Node> q;
il void add(int x,int y,int z) {
nx[++idx]=bg[x],bg[x]=idx,to[idx]=y,v[idx]=z;
}
il void dijkstra(int s)
{
memset(st,0,sizeof st);
while(!q.empty()) {
int x = q.top().x;
q.pop();
if(st[x]) continue;
st[x] = 1;
for(int i=bg[x];i;i=nx[i]) {
int y = to[i];
if(f[y][s]>f[x][s]+v[i]) {
f[y][s] = f[x][s]+v[i];
q.push({y, f[y][s]});
}
}
}
}
int main()
{
cin >> n >> m >> k;
for(int i=1,x,y,z;i<=m;++i) {
scanf("%d%d%d",&x,&y,&z);
add(x,y,z), add(y,x,z);
}
memset(f,0x3f,sizeof f);
for(int i=0,x;i<k;++i) {
scanf("%d",&x);
p[i]=x, f[x][(1<<i)]=0;
}
int S = 1<<k;
for(int s=1;s<S;++s) {
for(int i=1;i<=n;++i) {
for(int ss=s&(s-1);ss;ss=s&(ss-1))
f[i][s]=min(f[i][s], f[i][ss]+f[i][s^ss]);
if(f[i][s]<1e9) q.push({i, f[i][s]});
}
dijkstra(s);
}
cout << f[p[0]][S-1];
return 0;
}
[SCOI2009]围豆豆
-
看数据范围想到状压。
-
考虑射线法解决判断在矩形内部的问题。具体见这里 。
-
枚举起点,考虑 \(f[x][y][s]\) 为当前在 \((x, y)\),围住豆子的状态为 s 所用的最小长度,\(sum[s]\)为豆子状态为 s 时的豆子总和。
$ ans = \max (sum[s] - f[x][y][s])$ -
枚举所有\((x, y)\),初始化 \(f\)数组,\(vis\)数组,通过 bfs 求出最小长度即可,最后用
\(f[x][y][s]\) 更新答案(这样保证了是闭合图形)。
DP 优化
序列
题 意
非常神仙的动态规划,强烈推荐。
- 首先考虑,我们如何 得到/构造 所有情况,有一个 very nice 的构造方法 :如果我们将 1-n 点连成一条链,在任何时候,都可以加一条边将强连通分量变小成任意值。
- 考虑 dp ,我们需要维护哪些信息 :
- 当前加了几条边
- 当前有几个强连通分量
- B 数组的值分为几段(比如 B 数组为 : 5,4,4,2,1,1 ;则 B 的值有 5,4,2,1 四段。)
$\ \ $ Q : 前两状态显然,为什么要维护 B 的段数呢 ?
$\ \ $ A : 考虑段数每次变化,意味着至少增加了一条边,使某些点缩为一个强连通分量。后面会继续讲为什么。
- 由 2 我们可以设计状态 \(f[i][j][k]\) :当前加了 i 条边,有 j 个强连通分量,B 数组变化了 k 次的方案数。
- 当第 i 条边用于联通新的点时 :\(f[i][j][k]\gets f[i-1][j-1][k]\)。
- 当第 i 条边用于使前面的点成环时 :\(f[i][j][k]\gets f[i-1][h][k-1]\), \(h>j\)。
- 易得,要构造 j 个强连通分量,至少要用到这条链上的 n-j 条边,而最多有 i-k+1 条边用在链上 :\(i-k+1\ge n-j\)
- 然后我们又发现,由于这是简单图,所以当强连通分量个数为 j 时,边数是有上限的。可以发现,强连通分量个数为 j 的简单图的边数的最大值为 :\((n-j+1)\cdot(n-1)+(j-1)\cdot(j-2)/2\) 。
(即一个大小为 n-j+1 的连通块和 j-1 个大小为 1 的连通块。) - 因此 \(i\le (n-j+1)\cdot(n-1)+(j-1)\cdot(j-2)/2\)
- 现在有一个问题,时间复杂度为 \(O(n^4)\) (\(n^2\) 枚举边数,\(n^2\) 前缀和优化 dp)。
- 为了优化时间复杂度,我们挖掘这个 DP 的性质。考虑我们的状态 k 的意义何在 :只是为了判断 \(i-k+1\ge n-j\) 这个条件。
- 发现当 \(i > 2n\) 时 即\(i+j\ge n+k-1\) 无论 k 多少都满足。
- 当我们 dp 到 2n 时,令 \(g[2n][j]=\sum_{k=1}^{n}{f[2n][j][k]}\)。
- 让 i 从 2n+1 到 n(n-1) ,继续 dp :\(g[i][j]=\sum_{h\ge j}{g[i-1][h]}\) ,
且满足 :\(i\le (n-j+1)\cdot(n-1)+(j-1)\cdot(j-2)/2\)。
- 最后答案就是 f 或 g 求和。
数位 DP
[清华集训2016] 组合数问题
- 首先 n, m 很大,又因为要求组合数,自然想到 Lucas 定理 :$ C_n^m = C_{n/k}^{m/k} \times C_{n\ mod\ k}^{m\ mod\ k}\ (mod\ k)$
- 上面的式子显然是需要 k 进制拆分的,上式即 n,m 写作 k 进制形式,各个位的 C 的乘积 :\(\prod_{i}{C_{n_i}^{m_i}}\)
- 如果 \(C_{n}^{m}\) 是 k 的倍数,则上式为 0 ,对于任一项,若\(n_i<m_i\),则值为 0。
- 于是可以用数位 dp 解决问题,\(f(cur,ok,dif,dn,dm)\) 表示处理到 cur 位,当前是否已经合法,当前 i 是否大于 j,前cur位 i 是否与 n 相同,前 cur 位 j 是否与 m 相同的方案数。

dp者,仰之弥高,钻之弥坚,瞻之在前,忽焉在后。既竭吾才,如有所立卓尔。虽欲从之,末由也已。
浙公网安备 33010602011771号