Dynamic Programming

状态压缩DP

质数

题意 :\(n\) 个数,问从中最多选几个数,使选出的数两两互质,$ n \le 1000 ,\ a_i\le1000 $ .

  1. 小于 32 的质数有 11 个。
  2. \(a_i\) 中大于 32 的质因数最多有一个 (32*32>1000)。
  3. 因此,最大质因数小于 32 的数直接转移,大于 32 的数以其最大质因数分组转移,每组只能选一个,由 2. 知各组数无交集。
  4. 时间复杂度 $ O(2^{11}\cdot n)$ 。
  5. 和它十分相似的题 : [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. 首先 1 和所有数互质。因此对于任一个 \(i\)\(|a_i-b_i| \le |a_i-1|\) 否则令 \(b_i\) 等于 1 更优, 又因为 \(a_i \le 30\),因此 \(b_i<59\)
  2. 这样问题就便简单了,58 以内的质数只有 16 个,可以状压。
  3. \(f[i][S]\) 为考虑到 \(a\) 的第 i 位,已经用到的质数状态为 s 的最小值。
  4. 然后转移中顺便记录最优转移点,最后通过其还原 \(b\) 数组,就好了。
  5. 时间复杂度 \(O(n \cdot 2^{16} \cdot 58)\)

SRM713 DFSCount

题意 : 给定一个 n 个点的无向图,求不同 dfs 序个数,\(n \le 14\)

  1. 首先看数据范围想到状压。
  2. 考虑设计状态,\(f[i][s]\) 表示以 i 为根,当前子图态为 s 的方案数(第 x 个点在子树内则 s 第x位为 1,否则为 0)。
  3. 考虑先枚举点作为整棵树的根,设为 x ,然后枚举和 x 相连的连通块,设为 T1, T2, T3 。(将 x 去掉后图会分为若干连通块)(类似点分治)。
  4. 考虑 T1 ,设 T1 状态为 s, 设 T1 中与 x 有连边的点为 y1, y2,y3, 则 T1 的方案数为 \(f[y1][s]+f[y2][s]+f[y3][s] = Sum_{T1}\)
  5. 因此 x 的方案数为 \((Sum_{T1}+Sum_{T2}+Sum_{T3}) \cdot 3!\) (需要考虑 T1,T2,T3 的枚举顺序,因此乘组合数)。
  6. 于是递归到一点时,每次求分为几个连通块,按照 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\)

  1. 答案一定为一棵树,若存在环,删去环上任意一条边,边权变小。
  2. \(f[i][s]\) 为以 i 为根,联通,含有的关键点状态为 s(状压,有 i 则第 i 位为 1)的最小边权和。
  3. 对于 \(f[i][s]\) 有两种转移方式 :
    \(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f[j][s]+w[j][i] \to f[i][s]\)
    \(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f[i][t]+f[i][s-t] \to f[i][s]\)
  4. 第二种转移,直接枚举子集,复杂度 \(O(n\cdot 3^k)\), 第一种考虑类似最短路,对于每个 s 跑一遍 dijkstra 即可,复杂度 \(O(m\log m \cdot 2^k)\)
  5. 典型例题,[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]围豆豆

题 意

  1. 看数据范围想到状压。

  2. 考虑射线法解决判断在矩形内部的问题。具体见这里

  3. 枚举起点,考虑 \(f[x][y][s]\) 为当前在 \((x, y)\),围住豆子的状态为 s 所用的最小长度,\(sum[s]\)为豆子状态为 s 时的豆子总和。
    $ ans = \max (sum[s] - f[x][y][s])$

  4. 枚举所有\((x, y)\),初始化 \(f\)数组,\(vis\)数组,通过 bfs 求出最小长度即可,最后用
    \(f[x][y][s]\) 更新答案(这样保证了是闭合图形)。

DP 优化

序列

题 意
非常神仙的动态规划,强烈推荐。

  1. 首先考虑,我们如何 得到/构造 所有情况,有一个 very nice 的构造方法 :如果我们将 1-n 点连成一条链,在任何时候,都可以加一条边将强连通分量变小成任意值
  2. 考虑 dp ,我们需要维护哪些信息 :
  • 当前加了几条边
  • 当前有几个强连通分量
  • B 数组的值分为几段(比如 B 数组为 : 5,4,4,2,1,1 ;则 B 的值有 5,4,2,1 四段。)

$\ \ $ Q : 前两状态显然,为什么要维护 B 的段数呢 ?
$\ \ $ A : 考虑段数每次变化,意味着至少增加了一条边,使某些点缩为一个强连通分量。后面会继续讲为什么。

  1. 由 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\)
  1. 现在有一个问题,时间复杂度为 \(O(n^4)\) (\(n^2\) 枚举边数,\(n^2\) 前缀和优化 dp)。
  2. 为了优化时间复杂度,我们挖掘这个 DP 的性质。考虑我们的状态 k 的意义何在 :只是为了判断 \(i-k+1\ge n-j\) 这个条件。
  3. 发现当 \(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\)
  1. 最后答案就是 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 相同的方案数。
posted @ 2022-04-02 15:03  Aleksee  阅读(76)  评论(0)    收藏  举报