zoukankan      html  css  js  c++  java
  • $DP$ 重修

    (DP) 重修

    前言

    上一个 (dp) 的题目删掉了 那一个没写任何解析类的东西(就是懒) 重新练 (dp) 所以再开一个单章

    压行的话看着舒服就好 你对压行一无所知.png

    缩进的问题我是真的不会搞

    动态规划

    搞不懂怎么分类的就丢这了

    1. (P5664) (Emiya) 家今天的饭

    题意

    给定一个 (n imes m) 的矩阵 表示每个位置数可选的次数 每行最多选一个 每列选不能超过总数的一半 求方案数

    容斥

    答案为 每行选一个数的方案数减去每行选一个 有一行超过总数的一半的方案数

    一定只有一列超过总数的一半 直接枚举 设为 (p)

    状态 (f_{i, j, k}) 表示前 (i) 行 当前枚举列选择了 (j) 个数 其他列选择了 (k) 个数的方案数

    (g_{i, j}) 表示前 (i) 行选 (j) 个的方案数

    (s_i = sum_{j = 1}^m a_{i, j})

    转移 $$f_{i, j, k} = sum_{j = 0}^i sum_{k = 0}^i left(f_{i - 1, j, k} + f_{i - 1, j - 1, k} imes a_{i, p} + f_{i - 1, j, k - 1} imes left(s_i - a_{i, p} ight) ight)$$

    其中 (j + k leq i)

    [g_{i, j} = sum_{j = 0}^i left(g_{i - 1, j} + g_{i - 1, j - 1} imes s_i ight) ]

    答案为 $$sum_{j = 1}^n g_{n, j} - sum_{p = 1}^m sum_{j = 0}^n sum_{k = 0}^{j - 1} f_{n, j, k}$$

    (p) 直接在循环内统计 同样 (j + k leq n)

    显然 复杂度 (O(mn^3)) 可以通过 (84 Pts)

    这一部分的代码

    /*
      Time: 5.16
      Worker: Blank_space
      Source: P5664 [CSP-S2019] Emiya 家今天的饭
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define int long long
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 998244353;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, a[110][2021], g[110][110], s[110], f[110][110][110], sum, ans;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    signed main() {
    	n = read(); m = read(); g[0][0] = 1; f[0][0][0] = 1;
    	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) a[i][j] = read(), s[i] += a[i][j];
    	for(int i = 1; i <= n; i++) for(int j = 0; j <= i; j++) g[i][j] = (g[i][j] + g[i - 1][j] + (j > 0) * s[i] * g[i - 1][j - 1]) % mod;
    	for(int j = 1; j <= n; j++) ans = (ans + g[n][j]) % mod;
    	for(int p = 1; p <= m; p++, memset(f, 0, sizeof f), f[0][0][0] = 1)
    	{
    		for(int i = 1; i <= n; i++) for(int j = 0; j <= i; j++) for(int k = 0; k + j <= i; k++)
    			f[i][j][k] = (f[i][j][k] + f[i - 1][j][k] + a[i][p] * (j > 0) * f[i - 1][j - 1][k] % mod + (s[i] - a[i][p]) * (k > 0) * f[i - 1][j][k - 1] % mod) % mod;
    		for(int j = 0; j <= n; j++) for(int k = 0; k + j <= n && k < j; k++) sum = (sum + f[n][j][k]) % mod;
    	}
    	printf("%lld", (ans - sum + mod) % mod);
    	return 0;
    }
    

    考虑优化

    对于 (f) 的转移 与 (j)(k) 的大小无关 设 (f_{i, j}) 表示前 (i) 行 当前列比其他列多 (j) 个的方案数

    转移 $$f_{i, j} = sum_{j = n - i}^{n + i}left(f_{i - 1, j} + f_{i - 1, j - 1} imes a_{i, p} + f_{i - 1, j + 1} imes left(s_i - a_{i, p} ight) ight)$$

    复杂度 (O(mn^2)) 可以通过

    挂分小技巧

    (f) 数组第二维要开两倍 开小就是 (84) (Pts)

    代码

    /*
      Time: 5.16
      Worker: Blank_space
      Source: P5664 [CSP-S2019] Emiya 家今天的饭
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define int long long
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 998244353;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, a[110][2021], g[110][110], s[110], f[110][210], sum, ans;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    signed main() {
    	n = read(); m = read(); g[0][0] = 1; f[0][n] = 1;
    	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) a[i][j] = read(), s[i] = (s[i] + a[i][j] + mod) % mod;
    	for(int i = 1; i <= n; i++) for(int j = 0; j <= i; j++) g[i][j] = (g[i][j] + g[i - 1][j] + (j > 0) * s[i] * g[i - 1][j - 1] % mod) % mod;
    	for(int j = 1; j <= n; j++) ans = (ans + g[n][j]) % mod;
    	for(int p = 1; p <= m; p++, memset(f, 0, sizeof f), f[0][n] = 1)
    	{
    		for(int i = 1; i <= n; i++) for(int j = n - i; j <= n + i; j++)
    			f[i][j] = (f[i][j] + f[i - 1][j] + a[i][p] * (j > 0) * f[i - 1][j - 1] % mod + (s[i] - a[i][p]) * f[i - 1][j + 1] % mod) % mod;
    		for(int j = 1; j <= n; j++) sum = (sum + f[n][n + j]) % mod;
    	}
    	printf("%lld", (ans - sum + mod) % mod);
    	return 0;
    }
    
    

    线性 (dp)

    1. (P1521) 求逆序对

    (f_{i, j}) 表示 (i) 的全排列中 逆序对为 (j) 个的排列个数

    转移考虑插入第 (i) 个数的贡献

    (f_{i, j} = sum_{k = max{0, j - i + 1}}^j f[i - 1][k])

    /*
      Time: 4.28
      Worker: Blank_space
      Source: P1521 求逆序对
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e4;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, f[5010][5010];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    int main() {
    	n = read(); m = read();
    	for(int i = 1; i <= n; i++) f[i][0] = 1;
    	for(int i = 2; i <= n; i++) for(int j = 1; j <= m; j++)
    		for(int k = Max(0, j - i + 1); k <= j; k++) f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
    	printf("%d", f[n][m]);
    	return 0;
    }
    
    

    2. (P2513) 逆序对数列

    上一个题的加强版 (O(n^3)) 的复杂度吃不消的

    前缀和优化

    转移 (j) 的时候累加答案 超出范围的减掉

    /*
      Time: 4.28
      Worker: Blank_space
      Source: P2513 [HAOI2009]逆序对数列
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e4;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, f[1010][1010], sum;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    int main() {
    	n = read(); m = read();
    	for(int i = 1; i <= n; i++) f[i][0] = 1;
    	for(int i = 2; i <= n; i++, sum = 0) for(int j = 0; j <= m; j++)
    	{
    		sum = (sum + f[i - 1][j]) % mod; f[i][j] = sum % mod;
    		if(j - i + 1 >= 0) sum = (sum - f[i - 1][j - i + 1] + mod) % mod;
    	}
    	printf("%d", f[n][m]);
    	return 0;
    }
    
    

    背包 (dp)

    1. (P1941) 飞扬的小鸟

    背包 (dp) 向上按照完全背包转移 向下按照 (01) 背包转移

    细节很多 转移的边界比较烦人 比较亲切的是这个题并没有卡空间 (m) 比较小

    通过滚动数组优化成功吸氧卡上最优解

    挂分小技巧:

    滚动数组不清空

    代码:

    /*
      Time: 4.25
      Worker: Blank_space
      Source: P1941 [NOIP2014 提高组] 飞扬的小鸟
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, k, l[A], r[A], f[2][2010], x[A], y[A], ans = INF, cnt, id[A];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    int main() {
    	n = read(); m = read(); k = read(); memset(f, 63, sizeof f);
    	for(int i = 1; i <= m; i++) f[0][i] = 0;
    	for(int i = 1; i <= n; i++) x[i] = read(), y[i] = read(), l[i] = 1, r[i] = m;
    	for(int i = 1; i <= k; i++) {int z = read(); l[z] = read() + 1; r[z] = read() - 1; id[z] = i;}
    	for(int i = 1, p = 0; i <= n; i++, p = 0)
    	{
    		for(int j = 0; j <= m + x[i]; j++) f[i & 1][j] = INF;
    		for(int j = x[i] + 1; j <= m + x[i]; j++) f[i & 1][j] = Min(f[i + 1 & 1][j - x[i]], f[i & 1][j - x[i]]) + 1;
    		for(int j = m + 1; j <= m + x[i]; j++) f[i & 1][m] = Min(f[i & 1][m], f[i & 1][j]);
    		for(int j = 1; j <= m - y[i]; j++) f[i & 1][j] = Min(f[i & 1][j], f[i + 1 & 1][j + y[i]]);
    		for(int j = 1; j < l[i]; j++) f[i & 1][j] = INF;
    		for(int j = r[i] + 1; j <= m; j++) f[i & 1][j] = INF;
    		for(int j = 1; j <= m; j++) if(f[i & 1][j] < INF) p = 1;
    		if(!p) {printf("0
    %d
    ", cnt); return 0;}
    		if(id[i]) cnt++;
    	}
    	for(int i = 1; i <= m; i++) ans = Min(ans, f[n & 1][i]); printf("1
    %d
    ", ans);
    	return 0;
    }
    
    

    2. (P1156) 垃圾陷阱

    背包 (dp) (然而我并没有看出这是背包) 代码参照题解
    (f_i) 表示高度为 (i) 时所剩的生命
    对于垃圾
    吃: (f_i += f)
    堆: (f_{i + h} = max{f_{i + h}, f_i})

    注意能堆的条件以及滚动数组更新的顺序

    /*
      Time: 4.25
      Worker: Blank_space
      Source: P1156 垃圾陷阱
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, f[210];
    struct node {int h, f, t;} a[110];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    bool cmp(node x, node y) {return x.t < y.t;}
    /*----------------------------------------函数*/
    int main() {
    	m = read(); n = read(); f[0] = 10;
    	for(int i = 1; i <= n; i++) a[i].t = read(), a[i].f = read(), a[i].h = read();
    	std::sort(a + 1, a + 1 + n, cmp);
    	for(int i = 1; i <= n; i++) for(int j = m; j >= 0; j--) if(f[j] >= a[i].t)
    	{
    		if(j + a[i].h >= m) {printf("%d", a[i].t); return 0;}
    		f[j + a[i].h] = Max(f[j + a[i].h], f[j]); f[j] += a[i].f;
    	}
    	printf("%d", f[0]);
    	return 0;
    }
    
    

    3. (P5322) 排兵布阵

    与题解思路一致 但是我的挂了 转移出了点问题

    输入稍微改动一下 方便转移 (处理方法比较诡异) 想不到其他好的方法了 (a_{i, j}) 表示第 (i) 座城堡第 (j) 个人派出的兵力

    对每一个城堡的所有人派出的兵力排序 可以直接统计分数

    (f_{i, j}) 表示第 (i) 座城堡派出 (j) 的士兵最大分数 类似 (01) 背包转移

    代码:

    /*
      Time: 4.25
      Worker: Blank_space
      Source: P5322 [BJOI2019]排兵布阵
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int s, n, m, a[110][110], f[B << 1], ans;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    int main() {
    	s = read(); n = read(); m = read();
    	for(int i = 1; i <= s; i++) for(int j = 1; j <= n; j++) a[j][i] = read();
    	for(int i = 1; i <= n; i++)
    	{
    		std::sort(a[i] + 1, a[i] + 1 + s);
    		for(int j = m; j >= 0; j--)
    			for(int k = 1; k <= s; k++) if(j >= 2 * a[i][k] + 1)
    				f[j] = Max(f[j], f[j - 2 * a[i][k] - 1] + i * k);
    	}
    	for(int i = 0; i <= m; i++) ans = Max(ans, f[i]);
    	printf("%d", ans);
    	return 0;
    }
    
    

    4. (CF730J) (Bottles)

    又是一个看不出来的背包

    排序 暴力求出 (K)

    发现水的总量是一定的 不需要移动的水越多越优

    (f_{i, j, k}) 表示前 (i) 个瓶子总容量为 (j) 选择 (k) 个瓶子时最多的不需要移动的水

    (f_{i, j, k} = max{f_{i - 1, j - b_i, k - 1}})

    挂分小技巧:

    数组开小

    (dp) 数组木有初始化

    代码:

    /*
      Time: 4.28
      Worker: Blank_space
      Source: CF730J Bottles
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, f[A][110], tot, sum, K, ans;
    struct node {int x, y;} a[110];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    bool cmp(node x, node y) {return x.y == y.y ? x.x < y.x : x.y > y.y;}
    /*----------------------------------------函数*/
    int main() {
    	n = read();
    	for(int i = 1; i <= n; i++) a[i].x = read(), sum += a[i].x;
    	for(int i = 1; i <= n; i++) a[i].y = read();
    	std::sort(a + 1, a + 1 + n, cmp); memset(f, 128, sizeof f); f[0][0] = 0;
    	for(int i = 1; i <= n; i++) {tot += a[i].y; K++; if(tot >= sum) break;}
    	for(int i = 1; i <= n; i++) for(int j = tot; j >= a[i].y; j--)
    		for(int k = 1; k <= K; k++) f[j][k] = Max(f[j][k], f[j - a[i].y][k - 1] + a[i].x);
    	for(int i = sum; i <= tot; i++) ans = Max(ans, f[i][K]);
    	printf("%d %d", K, sum - ans);
    	return 0;
    }
    
    

    5. (P1450) 硬币购物

    很神的一道容斥题 思路不放 说不清楚

    容斥系数的正负很奇妙

    /*
      Time: 5.12
      Worker: Blank_space
      Source: P1450 [HAOI2008]硬币购物
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #define int long long
    /*--------------------------------------头文件*/
    const int S = 15;
    const int B = 1e5 + 7;
    /*------------------------------------常量定义*/
    int n, c[5], d[5], f[B], ans, a;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    signed main() {
    	for(int i = 1; i <= 4; i++) c[i] = read(); n = read(); f[0] = 1;
    	for(int i = 1; i <= 4; i++) for(int j = c[i]; j < B; j++) f[j] += f[j - c[i]];
    	while(n--)
    	{
    		for(int i = 1; i <= 4; i++) d[i] = read(); ans = f[a = read()];
    		for(int s = S, p = 0, k = 0; s; s = s - 1 & S, k = 0, p = 0)
    		{
    			for(int i = 1, j = 1; i <= 4; i++, j <<= 1) if(j & s)
    				k += c[i] * (d[i] + 1), p ^= 1;
    			if(a >= k) ans += p ? -f[a - k] : f[a - k];
    		}
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
    

    6. (P4095) (Eden) 的新背包问题

    多重背包 二进制优化

    前面背一次 后面背一次 卷一下即可

    /*
      Time: 5.16
      Worker: Blank_space
      Source: P4095 [HEOI2013]Eden 的新背包问题
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #define int long long
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, c[A], val[A], d[A], f[A][1010], g[A][1010], cnt;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    signed main() {
    	n = read();
    	for(int i = 1; i <= n; i++)
    	{
    		int x = read(), y = read(), z = read(), r = 1;
    		while(r <= z) {c[++cnt] = x * r, val[cnt] = y * r, d[cnt] = i, z -= r, r <<= 1;}
    		if(z) c[++cnt] = x * z, val[cnt] = y * z, d[cnt] = i;
    	}
    	m = read(); n = cnt;
    	for(int i = 1; i <= n; i++)
    	{
    		for(int j = 0; j <= 1000; j++) g[i][j] = g[i - 1][j];
    		for(int j = 1000; j >= c[i]; j--) g[i][j] = Max(g[i][j], g[i - 1][j - c[i]] + val[i]);
    	}
    	for(int i = n; i; i--)
    	{
    		for(int j = 0; j <= 1000; j++) f[i][j] = f[i + 1][j];
    		for(int j = 1000; j >= c[i]; j--) f[i][j] = Max(f[i][j], f[i + 1][j - c[i]] + val[i]);
    	}
    	for(int i = 1; i <= m; i++)
    	{
    		int p = read() + 1, v = read(), ans = 0, l = 0, r = 0;
    		while(d[l + 1] < p && l < n) l++; r = l;
    		while(d[r + 1] <= p && r < n) r++; r++;
    		for(int j = 0; j <= v; j++) ans = Max(ans, g[l][j] + f[r][v - j]);
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
    

    区间 (dp)

    1. (P4342) (Polygon)

    区间 (dp) 断环成链

    由于存在乘法 有负负得正的情况 所以维护一个最小值辅助 (dp) 转移

    挂分小技巧:

    字符的读入直接读字符串 (scanf) 的字符读入很难用

    代码:

    /*
      Time: 4.25
      Worker: Blank_space
      Source: P4342 [IOI1998]Polygon
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    int n, a[110], t[110], f[110][110], g[110][110], ans = -INF, cnt, d[110];
    char s[1];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    int main() {
    	n = read(); memset(f, 128, sizeof f); memset(g, 63, sizeof g);
    	for(int i = 1; i <= n; i++) {scanf("%s", s); g[i][i] = g[i + n][i + n] = f[i][i] = f[n + i][n + i] = read(); if(s[0] == 'x') t[i] = t[n + i] = 1;}
    	for(int i = 2; i <= n; i++) for(int l = 1, r = i; r <= n << 1; l++, r++)
    		for(int k = l; k < r; k++)
    			if(t[k + 1]) f[l][r] = Max(f[l][r], Max(f[l][k] * f[k + 1][r], Max(g[l][k] * g[k + 1][r], Max(f[l][k] * g[k + 1][r], g[l][k] * f[k + 1][r])))),
    				     g[l][r] = Min(g[l][r], Min(f[l][k] * f[k + 1][r], Min(g[l][k] * g[k + 1][r], Min(f[l][k] * g[k + 1][r], g[l][k] * f[k + 1][r]))));
    			else f[l][r] = Max(f[l][r], f[l][k] + f[k + 1][r]), g[l][r] = Min(g[l][r], g[l][k] + g[k + 1][r]);
    	for(int i = 1; i <= n; i++) ans = Max(ans, f[i][i + n - 1]); printf("%d
    ", ans);
    	for(int i = 1; i <= n; i++) if(f[i][i + n - 1] == ans) printf("%d ", i);
    	return 0;
    }
    
    

    2. (P4302) 字符串折叠

    转移的时候 不能压的长度直接相加 能压的考虑是否压缩
    要注意的一个是括号也算长度 所以不一定是压缩最优 另外 数字也算长度 而且不止有一位 预处理一下

    挂分小技巧:

    函数中传递字符数组下标从 (0) 开始

    代码:

    /*
      Time: 4.25
      Worker: Blank_space
      Source: P4302 [SCOI2003]字符串折叠
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int f[110][110], n, t[110];
    char s[110];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    bool check(char c[], int m, int l) {
    	for(int i = l; i < m; i++) if(c[i] != c[i % l]) return 0;
    	return 1;
    }
    /*----------------------------------------函数*/
    int main() {
    	scanf("%s", s + 1); n = strlen(s + 1); memset(f, 63, sizeof f); s[0] = '~';
    	for(int i = 1; i <= n; i++) f[i][i] = 1;
    	for(int i = 1; i <= 9; i++) t[i] = 1; for(int i = 10; i <= 99; i++) t[i] = 2; t[100] = 3;
    	for(int i = 2; i <= n; i++) for(int l = 1, r = i; r <= n; l++, r++)
    		for(int k = l; k < r; k++)
    		{
    			f[l][r] = Min(f[l][r], f[l][k] + f[k + 1][r]);
    			if(i % (k - l + 1)) continue;
    			if(check(s + l, i, k - l + 1)) f[l][r] = Min(f[l][r], f[l][k] + 2 + t[i / (k - l + 1)]);
    		}
    	printf("%d", f[1][n]);
    	return 0;
    }
    
    

    3. (UVA11022 String Factoring)

    区间 (dp) + (KMP) (其实可以不用的 毕竟 (n) 四方都能过)

    状态: (f_{l, r}) 表示将区间 ([l, r]) 所能压缩成的字符个数

    先预处理 把每个区间 ([l, r])(nxt) 数组求出来 转移的时候判断循环节进行压缩 在进行区间合并

    代码

    /*
      Time: 6.5
      Worker: Blank_space
      Source:
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    inline void File() {
    	freopen(".in", "r", stdin);
    	freopen(".out", "w", stdout);
    }
    /*----------------------------------------文件*/
    char s[110];
    int n, p[110][110], f[110][110];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void KMP() {
    	memset(p, 0, sizeof p);
    	for(int k = 1; k <= n; k++) for(int i = k, j = 0; i <= n; i++)
    	{
    		while(j && s[i + 1] != s[k + j]) j = p[k][j];
    		p[k][i + 1] = s[i + 1] == s[k + j] ? ++j : j;
    	}
    }
    void DP() {
    	for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) f[i][j] = j - i + 1;
    	for(int i = 2; i <= n; i++) for(int l = 1, r = i, t = p[l][r]; r <= n; l++, r++, t = p[l][r])
    	{
    		for(; t; t = p[l][t]) if(!(i % (i - t))) f[l][r] = Min(f[l][r], f[l][l + i - t - 1]);
    		for(int k = l; k < r; k++) f[l][r] = Min(f[l][r], f[l][k] + f[k + 1][r]);
    	}
    }
    /*----------------------------------------函数*/
    int main() {
    	while(1)
    	{
    		scanf("%s", s + 1); if(s[1] == '*') return 0;
    		n = strlen(s + 1); KMP(); DP();
    		printf("%d
    ", f[1][n]);
    	}
    	return 0;
    }
    
    

    状压 (dp)

    1. (P6192) 最小斯坦纳树

    模板题

    时隔许久 又开始写新的板子了

    (思路及代码均来自题解)

    (f_{i, S}) 表示以 (i) 为根 连接点集为 (S) 的最小代价

    转移考虑两种情况 即将一个点连过来 或 将两棵树合并 有

    [f_{i, S} = egin{cases}f_{i, S'} + f_{i, S - S'} \ f_{j, S'} + dis_{i, j} end{cases} ]

    上面那个东西枚举子集 下面那个东西跑最短路

    还是不是很理解

    /*
      Time: 5.8
      Worker: Blank_space
      Source: P6192 【模板】最小斯坦纳树
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, k, f[510][(1 << 10) + 5], t;
    struct edge {int v, w, nxt;} e[1010];
    int head[110], ecnt;
    bool vis[110];
    std::priority_queue <std::pair <int, int> > q;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v, int w) {e[++ecnt] = (edge){v, w, head[u]}; head[u] = ecnt;}
    void dijk(int s) {
    	memset(vis, 0, sizeof vis);
    	while(!q.empty())
    	{
    		std::pair <int, int> a = q.top(); int u = a.second; q.pop();
    		if(vis[u]) continue; vis[u] = 1;
    		for(int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v)
    			if(f[v][s] > f[u][s] + e[i].w) f[v][s] = f[u][s] + e[i].w, q.push(std::make_pair(-f[v][s], v));
    	}
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read(); m = read(); k = read();
    	for(int i = 1; i <= m; i++)
    	{
    		int x = read(), y = read(), z = read();
    		add_edge(x, y, z); add_edge(y, x, z);
    	}
    	memset(f, 63, sizeof f); t = read(); f[t][1] = 0;
    	for(int i = 2; i <= k; i++) f[read()][1 << i - 1] = 0;
    	for(int S = 1; S < 1 << k; dijk(S), S++) for(int i = 1; i <= n; i++)
    	{
    		for(int s = S & S - 1; s; s = S & s - 1)
    			f[i][S] = Min(f[i][S], f[i][s] + f[i][S ^ s]);
    		if(f[i][S] != INF) q.push(std::make_pair(-f[i][S], i));
    	}
    	printf("%d
    ", f[t][(1 << k) - 1]);
    	return 0;
    }
    

    2. (P4294) 游览计划

    最小斯坦纳树的模板题 我是真的烦路径打印

    对于一个格子 向四个方向连边 映射一下 注意转移的时候的重复计算 需要减掉

    /*
      Time: 5.8
      Worker: Blank_space
      Source: P4294 [WC2008]游览计划
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, f[110][(1 << 10) + 5], a[110], p[15], cnt, pre[110][(1 << 10) + 5];
    struct edge {int v, nxt;} e[510];
    int head[110], ecnt;
    bool vis[110], _vis[110][(1 << 10) + 5], mp[110];
    std::priority_queue <std::pair <int, int> > q;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
    int d(int x, int y) {return (x - 1) * m + y;}
    void dijk(int s) {
    	memset(vis, 0, sizeof vis);
    	while(!q.empty())
    	{
    		int u = q.top().second; q.pop();
    		if(vis[u]) continue; vis[u] = 1;
    		for(int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v)
    			if(f[v][s] > f[u][s] + a[v]) f[v][s] = f[u][s] + a[v], q.push(std::make_pair(-f[v][s], v)), pre[v][s] = u;
    	}
    }
    void dfs(int u, int S) {
    	if(_vis[u][S]) return ; _vis[u][S] = 1; mp[u] = 1;
    	if(pre[u][S] && f[pre[u][S]][S] + a[u] == f[u][S]) {int t = pre[u][S]; while(t) dfs(t, S), t = pre[t][S];}
    	for(int s = S & S - 1; s; s = S & s - 1) if(f[u][S] == f[u][s] + f[u][S ^ s] - a[u]) {dfs(u, s); dfs(u, s ^ S); break;}
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read(); m = read(); memset(f, 63, sizeof f);
    	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++)
    	{
    		a[d(i, j)] = read();
    		if(i > 1) add_edge(d(i, j), d(i - 1, j)), add_edge(d(i - 1, j), d(i, j));
    		if(j > 1) add_edge(d(i, j), d(i, j - 1)), add_edge(d(i, j - 1), d(i, j));
    		if(!a[d(i, j)]) p[++cnt] = d(i, j), f[p[cnt]][1 << cnt - 1] = 0;
    	}
    	for(int S = 1; S < 1 << cnt; dijk(S), S++) for(int i = 1; i <= n * m; i++)
    	{
    		for(int s = S & S - 1; s; s = S & s - 1) f[i][S] = Min(f[i][S], f[i][s] + f[i][S ^ s] - a[i]);
    		if(f[i][S] != INF) q.push(std::make_pair(-f[i][S], i));
    	}
    	printf("%d
    ", f[p[1]][(1 << cnt) - 1]); dfs(p[1], (1 << cnt) - 1);
    	for(int i = 1; i <= n; i++, puts("")) for(int j = 1; j <= m; j++)
    	{
    		if(a[d(i, j)] && mp[d(i, j)]) printf("o");
    		if(a[d(i, j)] && !mp[d(i, j)]) printf("_");
    		if(!a[d(i, j)]) printf("x");
    	}
    	return 0;
    }
    
    

    3. (CF453B Little Pony and Harmony Chest)

    所有的元素互质 也就是说不能有相同的质因子

    发现 (a) 只有 (30) 那么 (b) 一定不可能超过 (60) 否则一定可以选择一个小于 (60) 的数使答案更优 质因子自然不会很大了

    (60) 以内最大的质因子是 (59) 发现这玩意和 (1) 是等价的 所以最大的有用的质因子为 (53) 懒得写筛法 所以直接打表了

    对于 (58) 以内的所有数 用二进制表示其质因子 进行状压 (dp) 直接枚举子集 还是都来一遍就随意了

    记录路径 烦...

    最优解 (rank1)

    比第二快了一倍

    /*
      Time: 5.5
      Worker: Blank_space
      Source: CF453B Little Pony and Harmony Chest
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, f[110][(1 << 16) + 5], a[110], s[60], g[110][(1 << 16) + 5], min = INF, _s, ans[110];
    int p[20] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
    bool vis[60];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void dfs(int i, int c) {
    	if(!i) return ; dfs(i - 1, c ^ s[g[i][c]]);
    	printf("%d ", g[i][c]);
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read(); memset(f, 63, sizeof f); f[0][0] = 0;
    	for(int i = 1; i <= n; i++) a[i] = read();
    	for(int i = 1; i <= 58; i++) for(int j = 1, k = 1; j <= 16; j++, k <<= 1)
    		if(!(i % p[j])) s[i] |= k;
    	for(int i = 1; i <= n; i++) for(int k = 1; k <= 58; k++)
    	{
    		int S = (1 << 16) - 1 ^ s[k];
    		for(int b = S; b >= 0; b = b - 1 & S)
    		{
    			if(f[i][b | s[k]] > f[i - 1][b] + Abs(a[i] - k))
    				f[i][b | s[k]] = f[i - 1][b] + Abs(a[i] - k), g[i][b | s[k]] = k;
    			if(!b) break;
    		}
    	}
    	for(int b = 0; b < 1 << 16; b++) if(f[n][b] < min) min = f[n][b], _s = b;
    	for(int i = n; i; --i) ans[i] = g[i][_s], _s ^= s[g[i][_s]];
    	for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
    	return 0;
    }
    

    数位 (dp)

    树形 (dp)

    1. (P4362) 贪吃的九头龙

    树形 (dp)

    我上去就是一个 n 四方的转移

    树形 (dp) 一般形式 (f_u) 表示节点

    大头要吃够 (K) 个果子 (f_{u, j}) 表示以 (u) 为根的子树内大头吃掉了 (j) 个果子

    要求 (1) 号果子只能由大头来吃 (f_{u, j, 0/1}) 表示以 (u) 为根的子树内 大头吃掉了 (j) 个果子 (u) 号果子是否是大头吃的

    转移的时候判断是否同为大头吃的计算贡献 特判 (m = 2) 的情况

    数组转移的时候需要重复用到 要复制出来 不然会越来越小

    /*
      Time: 4.28
      Worker: Blank_space
      Source: P4362 [NOI2002] 贪吃的九头龙
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, K, f[310][310][2], g[310][2];
    struct edge {int v, w, nxt;} e[A];
    int head[310], ecnt;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v, int w) {e[++ecnt] = (edge){v, w, head[u]}; head[u] = ecnt;}
    void dfs(int u, int pre) {
    	f[u][1][1] = f[u][0][0] = 0;
    	for(int i = head[u]; i; i = e[i].nxt)
    	{
    		int v = e[i].v, w = e[i].w; if(v == pre) continue;
    		dfs(v, u); memcpy(g, f[u], sizeof f[u]); memset(f[u], 63, sizeof f[u]);
    		for(int j = 0; j <= K; j++) for(int k = 0; k <= j; k++)
    			f[u][j][0] = Min(f[u][j][0], Min(f[v][k][1] + g[j - k][0], f[v][k][0] + g[j - k][0] + (m == 2) * w)),
    			f[u][j][1] = Min(f[u][j][1], Min(f[v][k][0] + g[j - k][1], f[v][k][1] + g[j - k][1] + w));
    	}
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read(); m = read(); K = read(); memset(f, 63, sizeof f);
    	if(n - K < m - 1) {puts("-1"); return 0;}
    	for(int i = 1; i < n; i++)
    	{
    		int x = read(), y = read(), z = read();
    		add_edge(x, y, z); add_edge(y, x, z);
    	}
    	dfs(1, 0); printf("%d", f[1][K][1]);
    	return 0;
    }
    
    

    2. (P2016) 战略游戏(最小权覆盖集)

    一道模板题

    /*
      Time: 4.28
      Worker: Blank_space
      Source: P2016 战略游戏(最小权覆盖集) 
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define int long long
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, f[2021][2];
    struct edge {int v, nxt;} e[A];
    int head[2021], ecnt;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
    void dfs(int u, int pre) {
    	f[u][1] = 1;
    	for(int i = head[u]; i; i = e[i].nxt)
    	{
    		int v = e[i].v; if(v == pre) continue; dfs(v, u);
    		f[u][1] += Min(f[v][0], f[v][1]); f[u][0] += f[v][1];
    	}
    }
    /*----------------------------------------函数*/
    signed main() {
    	n = read();
    	for(int i = 1; i <= n; i++)
    	{
    		int x = read() + 1, k = read();
    		for(int j = 1; j <= k; j++) {int y = read() + 1; add_edge(x, y); add_edge(y, x);}
    	}
    	dfs(1, 0); printf("%lld", Min(f[1][0], f[1][1]));
    	return 0;
    }
    
    

    3. (P1352) 没有上司的舞会(最大权独立集)

    模板题

    /*
      Time: 4.28
      Worker: Blank_space
      Source: P1352 没有上司的舞会(最大权独立集) 
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, a[A], f[A][2];
    struct edge {int v, nxt;} e[A << 1];
    int head[A], ecnt;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
    void dfs(int u, int pre) {
    	f[u][1] = a[u];
    	for(int i = head[u]; i; i = e[i].nxt)
    	{
    		int v = e[i].v; if(v == pre) continue; dfs(v, u);
    		f[u][0] += Max(f[v][0], f[v][1]); f[u][1] += f[v][0];
    	}
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read();
    	for(int i = 1; i <= n; i++) a[i] = read();
    	for(int i = 1; i < n; i++)
    	{
    		int x = read(), y = read();
    		add_edge(x, y); add_edge(y, x);
    	}
    	dfs(1, 0); printf("%d", Max(f[1][0], f[1][1]));
    	return 0;
    }
    
    

    4. (P4253) 小凸玩密室

    这个题的第一个题解是我到现在见过最神的一个思路 受限于水平不足 理解了一个小时才看懂到底是什么意思

    (a_i) 表示 (i) 点的点权

    (ls_i) (rs_i) 表示一个点的 左 右 儿子

    (dis_{i, j}) 表示 (i) 点到 (i) 点的第 (j) 个祖先的距离

    函数 (bro(i, j)) 表示 (i) 点的第 (j) 个祖先的另一个儿子

    状态

    (f_{i, j}) 表示点亮 (i) 点后 回到 (i) 点的第 (j) 个祖先的最小代价

    (g_{i, j}) 表示点亮 (i) 点后 回到 (i) 点的第 (j) 个祖先的另一个儿子的最小代价

    转移

    [g_{i, j} = egin{cases} left(dis_{i, j} + dis_{bro(i, j), 1} ight) imes a_{bro(i, j)} & 该点为叶子节点\ dis_{ls_i, 1} imes a_{ls_i} + g_{ls_i, j + 1} & 该点只有左儿子\ min{dis_{ls_i, 1} imes a_{ls_i} + g_{ls_i, 1} + g_{rs_i, j + 1}, dis_{rs_i, 1} imes a_{rs_i} + g_{rs_i, 1} + g_{ls_i, j + 1}, } & 该点有两个儿子 end{cases} ]

    [f_{i, j} = egin{cases} dis_{i, j} imes a_{i >> j} & 该点为叶子节点\ dis_{ls_i, 1} imes a_{ls_i} + f_{ls_i, j + 1} & 该点只有左儿子\ min{dis_{ls_i, 1} imes a_{ls_i} + g_{ls_i, 1} + f_{rs_i, j + 1}, dis_{rs_i, 1} imes a_{rs_i} + g_{rs_i, 1} + f_{ls_i, j + 1} } & 该点有两个儿子 end{cases} ]

    然后枚举每个点 对每个点向上累计答案 取最小值

    然后这个题的值真的很大 (ans) 的初始值开小了是一个点都过不去的... 不要问怎么知道的

    /*
      Time: 5.19
      Worker: Blank_space
      Source: P4253 [SCOI2015]小凸玩密室
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define int long long
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int INF = 1e18;
    /*------------------------------------常量定义*/
    int n, a[B << 1], ls[B << 1], rs[B << 1], f[B << 1][20], g[B << 1][20], dis[B << 1][20], ans = INF;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    int bro(int i, int j) {return i >> j - 1 ^ 1;}
    /*----------------------------------------函数*/
    signed main() {
    	n = read();
    	for(int i = 1; i <= n; i++) a[i] = read();
    	for(int i = 2; i <= n; i++) dis[i][1] = read();
    	for(int i = 1; i <= (n >> 1) + 1; i++)
    	{
    		if((i << 1) <= n) ls[i] = i << 1; else break;
    		if((i << 1 | 1) <= n) rs[i] = i << 1 | 1;
    	}
    	for(int j = 2; j <= 18; j++) for(int i = n; i >> j; i--) dis[i][j] = dis[i][j - 1] + dis[i >> j - 1][1];
    	for(int i = n; i; i--)
    		if(!ls[i]) for(int j = 1; i >> j - 1; j++) g[i][j] = (dis[i][j] + dis[bro(i, j)][1]) * a[bro(i, j)];
    		else if(!rs[i]) for(int j = 1; i >> j - 1; j++) g[i][j] = dis[ls[i]][1] * a[ls[i]] + g[ls[i]][j + 1];
    		else for(int j = 1; i >> j - 1; j++) g[i][j] = Min(dis[ls[i]][1] * a[ls[i]] + g[ls[i]][1] + g[rs[i]][j + 1], dis[rs[i]][1] * a[rs[i]] + g[rs[i]][1] + g[ls[i]][j + 1]);
    	for(int i = n; i; i--)
    		if(!ls[i]) for(int j = 1; i >> j - 1; j++) f[i][j] = dis[i][j] * a[i >> j];
    		else if(!rs[i]) for(int j = 1; i >> j - 1; j++) f[i][j] = dis[ls[i]][1] * a[ls[i]] + f[ls[i]][j + 1];
    		else for(int j = 1; i >> j - 1; j++) f[i][j] = Min(dis[ls[i]][1] * a[ls[i]] + g[ls[i]][1] + f[rs[i]][j + 1], dis[rs[i]][1] * a[rs[i]] + g[rs[i]][1] + f[ls[i]][j + 1]);
    	for(int i = 1, sum = f[1][1]; i <= n; i++, ans = Min(ans, sum), sum = f[i][1])
    		for(int j = 1; i >> j; j++)
    			if(bro(i, j) > n) sum += dis[i >> j][1] * a[i >> j + 1];
    			else sum += dis[bro(i, j)][1] * a[bro(i, j)] + f[bro(i, j)][2];
    	printf("%lld", ans);
    	return 0;
    }
    
    

    树上背包

    1. (P3360) 偷天换日

    树形 (+) (01)

    存图的方式有点诡异 真的想不到

    是画室 (01) 背包转移 否则考虑左右孩子进行转移

    /*
      Time: 4.28
      Worker: Blank_space
      Source: P3360 偷天换日
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define ls(x) x << 1
    #define rs(x) x << 1 | 1
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int v, f[1010][1010], c[1010], val[1010];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void dfs(int p) {
    	int t = read() << 1, k = read();
    	if(k)
    	{
    		for(int i = 1; i <= k; i++) val[i] = read(), c[i] = read();
    		for(int i = 1; i <= k; i++)
    			for(int j = v; j >= t + c[i]; j--) f[p][j] = Max(f[p][j], f[p][j - c[i]] + val[i]);
    	}
    	else
    	{
    		dfs(ls(p)); dfs(rs(p));
    		for(int i = v; i >= t; i--)
    			for(int j = 0; j <= i - t; j++) f[p][i] = Max(f[p][i], f[ls(p)][j] + f[rs(p)][i - j - t]);
    	}
    }
    /*----------------------------------------函数*/
    int main() {
    	v = read() - 1; dfs(1); printf("%d", f[1][v]);
    	return 0;
    }
    
    

    2. (P1272) 重建道路

    比较难受的初始化 没有考虑到

    类?树形背包 大概

    (f_{u, j}) 表示包括 (u) 号点的连通块中有 (j) 个点时切去的最小边数

    初始化 (f_{u, 1}) 为自己的度 相当于将所有的边全部斩断 只剩下自己

    转移按照树形背包一般形式转移

    (f_{u, j} = max_{j in [1, p], k in [1, j)}{f_{v, k} + f_{u, j - k} - 2})

    这里的这个 (-2) 比较恶心 是因为初始化的时候 (u o v) 的边和 (v o u) 的边都是算了贡献的 这两点联通的时候要将这两点的贡献都减掉

    说不清楚的感觉

    在扯一下

    就是减去 (f_u) 的贡献 同时 减去 (f_v) 的贡献

    /*
      Time: 4.28
      Worker: Blank_space
      Source: P1272 重建道路
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, p, f[200][200], du[200], ans = INF;
    struct edge {int v, nxt;} e[A << 1];
    int head[200], ecnt;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
    void dfs(int u, int pre) {
    	f[u][1] = du[u];
    	for(int i = head[u]; i; i = e[i].nxt)
    	{
    		int v = e[i].v; if(v == pre) continue; dfs(v, u);
    		for(int j = p; j; j--) for(int k = 1; k < j; k++)
    			f[u][j] = Min(f[u][j], f[v][k] + f[u][j - k] - 2);
    	}
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read(); p = read(); memset(f, 63, sizeof f);
    	for(int i = 1; i < n; i++)
    	{
    		int x = read(), y = read(); du[x]++; du[y]++;
    		add_edge(x, y); add_edge(y, x);
    	}
    	dfs(1, 0);
    	for(int i = 1; i <= n; i++) ans = Min(ans, f[i][p]);
    	printf("%d", ans);
    	return 0;
    }
    
    

    3. (CF581F Zublicanes and Mumocrates)

    (f_{u, j, 0/1}) 表示以 (u) 为根的子树中有 (j) 个叶子节点染成黑色 当前节点的颜色

    其实黑的白的无所谓 只要一个满足了 另一个就满足了 转移按照树上背包转移 进行背包合并

    标记叶子节点进行初始化

    /*
      Time: 5.12
      Worker: Blank_space
      Source: CF581F Zublicanes and Mumocrates
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, f[5010][5010][2], out[5010], siz[5010], size, rt = 1;
    struct edge {int v, nxt;} e[A];
    int head[5010], ecnt;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
    void dfs1(int u, int pre) {
    	for(int i = head[u]; i; i = e[i].nxt)
    	{
    		int v = e[i].v; out[u]++;
    		if(v == pre) continue;
    		dfs1(v, u);
    	}
    	size += out[u] == 1;
    }
    void dfs2(int u, int pre) {
    	if(out[u] == 1) {siz[u] = 1; f[u][0][0] = f[u][1][1] = 0; return ;}
    	else f[u][0][0] = f[u][0][1] = 0;
    	for(int i = head[u]; i; i = e[i].nxt)
    	{
    		int v = e[i].v; if(v == pre) continue;
    		dfs2(v, u); siz[u] += siz[v];
    		for(int j = siz[u]; j >= 0; j--)
    		{
    			int tmp0 = INF, tmp1 = INF;
    			for(int k = 0; k <= siz[v]; k++) if(k <= j)
    				tmp0 = Min(tmp0, f[u][j - k][0] + Min(f[v][k][0], f[v][k][1] + 1)),
    				tmp1 = Min(tmp1, f[u][j - k][1] + Min(f[v][k][1], f[v][k][0] + 1));
    			f[u][j][0] = tmp0; f[u][j][1] = tmp1;
    		}
    	}
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read(); memset(f, 63, sizeof f);
    	for(int i = 1; i < n; i++)
    	{
    		int x = read(), y = read();
    		add_edge(x, y); add_edge(y, x);
    	}
    	dfs1(1, 0); while(out[rt] == 1) rt++; dfs2(rt, 0);
    	printf("%d", Min(f[rt][size >> 1][0], f[rt][size >> 1][1]));
    	return 0;
    }
    
    

    换根 (dp)

    基环树 (dp)

    虚树

    1. (P2495) 消耗战

    (刚学虚树.jpg)

    (f_u) 表示断开 (u) 点与根节点的链接的最小代价

    转移就是这条链上的最小值

    (dp) 式子不是太难 只是时间爆炸

    通过构建虚树优化时间

    /*
      Time: 4.30
      Worker: Blank_space
      Source: P2495 [SDOI2011]消耗战
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<vector>
    #include<algorithm>
    #define int long long
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 250010;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 1e18;
    /*------------------------------------常量定义*/
    int n, m, d[B], dfn[B], dep[B], minw[B], st[B], top;
    struct edge {int v, w, nxt;} e[B << 1];
    int head[B], ecnt;
    std::vector <int> q[B];
    bool vis[B];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v, int w) {e[++ecnt] = (edge){v, w, head[u]}; head[u] = ecnt;}
    bool cmp(int x, int y) {return dfn[x] < dfn[y];}
    namespace Cut {
    	int fa[B], top[B], siz[B], son[B], cnt;
    	void dfs1(int u, int pre) {
    		dep[u] = dep[pre] + 1; fa[u] = pre; siz[u] = 1;
    		for(int i = head[u]; i; i = e[i].nxt)
    		{
    			int v = e[i].v, w = e[i].w; if(v == pre) continue;
    			minw[v] = Min(minw[u], w); dfs1(v, u); siz[u] += siz[v];
    			if(!son[u] || siz[son[u]] < siz[v]) son[u] = v;
    		}
    	}
    	void dfs2(int u, int tp) {
    		dfn[u] = ++cnt; top[u] = tp;
    		if(!son[u]) return ; dfs2(son[u], tp);
    		for(int i = head[u]; i; i = e[i].nxt)
    		{
    			int v = e[i].v; if(v == fa[u] || v == son[u]) continue;
    			dfs2(v, v);
    		}
    	}
    	int LCA(int x, int y) {
    		for(; top[x] != top[y]; x = fa[top[x]]) if(dep[top[x]] < dep[top[y]]) Swap(x, y);
    		return dep[x] < dep[y] ? x : y;
    	}
    }
    void add(int x) {
    	int lca = Cut::LCA(x, st[top]);
    	for(; dep[st[top - 1]] > dep[lca]; --top) q[st[top - 1]].push_back(st[top]);
    	if(lca != st[top]) {q[lca].push_back(st[top--]); if(lca != st[top]) st[++top] = lca;}
    	st[++top] = x;
    }
    int dfs(int u, int sum = 0) {
    	for(int i = 0; i < q[u].size(); i++) sum += dfs(q[u][i]);
    	q[u].clear(); if(vis[u]) {vis[u] = 0; return minw[u];}
    	return Min(minw[u], sum);
    }
    /*----------------------------------------函数*/
    signed main() {
    	n = read(); minw[1] = INF;
    	for(int i = 1; i < n; i++)
    	{
    		int x = read(), y = read(), z = read();
    		add_edge(x, y, z); add_edge(y, x, z);
    	}
    	Cut::dfs1(1, 0); Cut::dfs2(1, 1); m = read();
    	while(m--)
    	{
    		int t = read();
    		for(int i = 1; i <= t; i++) d[i] = read(), vis[d[i]] = 1;
    		std::sort(d + 1, d + 1 + t, cmp); st[top = 0] = 1;
    		for(int i = 1; i <= t; i++) add(d[i]);
    		for(; top; --top) q[st[top - 1]].push_back(st[top]);
    		printf("%lld
    ", dfs(1));
    	}
    	return 0;
    }
    
    

    2. (P4103) 大工程

    三个问题 (k) 个点对之间的长度之和 最长链 最短链

    (f_u) 表示以 (u) 点为根的子树中关键点对之间的长度之和

    (g_u) 表示以 (u) 点为根的子树中关键点对到 (u) 点的距离之和

    (f_u = sum f_v + left(g_v + size_v imes dis_{u, v} ight) imes left(size_u - size_v ight))

    构建虚树直接 (dp)

    挂分小技巧:

    栈中一开始 (top = 0) 时只有一号点 向前取的时候会出现越界的现象 容易发生奇奇怪怪的错误 数组越界调了一晚上 代码逐渐题解化

    可以将栈底设为 (1) 另一种方法是不要把栈数组放到第一个 让它越界到其他没有信息的数组一般不会有太大问题

    代码:

    /*
      Time: 4.30
      Worker: Blank_space
      Source: P4103 [HEOI2014]大工程
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<vector>
    #include<algorithm>
    #define int long long
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, dep[C], dfn[C], siz[C];
    struct edge {int v, nxt;} e[C << 1];
    int head[C], ecnt;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void add_edge(int u, int v) {e[++ecnt] = (edge){v, head[u]}; head[u] = ecnt;}
    bool cmp(int x, int y) {return dfn[x] < dfn[y];}
    namespace Cut {
    	int top[C], son[C], fa[C], cnt;
    	void dfs1(int u, int pre) {
    		fa[u] = pre; dep[u] = dep[pre] + 1; siz[u] = 1;
    		for(int i = head[u]; i; i = e[i].nxt)
    		{
    			int v = e[i].v; if(v == pre) continue;
    			dfs1(v, u); siz[u] += siz[v];
    			if(!son[u] || siz[son[u]] < siz[v]) son[u] = v;
    		}
    	}
    	void dfs2(int u, int tp) {
    		top[u] = tp; dfn[u] = ++cnt;
    		if(!son[u]) return ; dfs2(son[u], tp);
    		for(int i = head[u]; i; i = e[i].nxt)
    		{
    			int v = e[i].v; if(v == fa[u] || v == son[u]) continue;
    			dfs2(v, v);
    		}
    	}
    	int LCA(int x, int y) {
    		for(; top[x] != top[y]; x = fa[top[x]]) if(dep[top[x]] < dep[top[y]]) Swap(x, y);
    		return dep[x] < dep[y] ? x : y;
    	}
    }
    namespace VT {
    	int d[C], st[C], top, sumdis[C], maxdis[C], mindis[C], f1[C], f2[C], f3[C];
    	std::vector <int> q[C];
    	bool vis[C];
    	void add(int x) {
    		int lca = Cut::LCA(x, st[top]);
    		for(; dep[st[top - 1]] > dep[lca]; --top)
    		q[st[top - 1]].push_back(st[top]);
    		if(lca != st[top]) {q[lca].push_back(st[top--]); if(lca != st[top]) st[++top] = lca;}
    		if(st[top] != x) st[++top] = x;
    	}
    	void build(int t) {
    		for(int i = 1; i <= t; i++) d[i] = read(), vis[d[i]] = 1;
    		std::sort(d + 1, d + 1 + t, cmp); st[top = 0] = 1;
    		for(int i = 1; i <= t; i++) add(d[i]);
    		for(; top; --top) q[st[top - 1]].push_back(st[top]);
    	}
    	void dfs(int u) {
    		f1[u] = f3[u] = 0; f2[u] = INF; siz[u] = vis[u];
    		sumdis[u] = 0; maxdis[u] = vis[u] ? 0 : -INF; mindis[u] = vis[u] ? 0 : INF;
    		for(int i = 0; i < q[u].size(); i++)
    		{
    			int v = q[u][i], dis = dep[v] - dep[u];	dfs(v);
    			siz[u] += siz[v]; f2[u] = Min(f2[u], f2[v]); f3[u] = Max(f3[u], f3[v]);
    			sumdis[u] += sumdis[v] + siz[v] * dis; mindis[u] = Min(mindis[u], mindis[v] + dis); maxdis[u] = Max(maxdis[u], maxdis[v] + dis);
    		}
    		int max1 = -1, max2 = -1, min1 = INF, min2 = INF; if(vis[u]) max1 = min1 = 0;
    		for(int i = 0; i < q[u].size(); i++)
    		{
    			int v = q[u][i], dis = dep[v] - dep[u];
    			f1[u] += f1[v] + (sumdis[v] + siz[v] * dis) * (siz[u] - siz[v]);
    			if(max1 < maxdis[v] + dis) max2 = max1, max1 = maxdis[v] + dis;
    			else if(max2 < maxdis[v] + dis) max2 = maxdis[v] + dis;
    			if(min1 > mindis[v] + dis) min2 = min1, min1 = mindis[v] + dis;
    			else if(min2 > mindis[v] + dis) min2 = mindis[v] + dis;
    		}
    		if(min1 != INF && min2 != INF) f2[u] = Min(f2[u], min1 + min2);
    		if(max1 != -1 && max2 != -1) f3[u] = Max(f3[u], max1 + max2);
    		vis[u] = 0; q[u].clear();
    	}
    	void solve(int t) {
    		build(t);
    		dfs(1);
    		printf("%lld %lld %lld
    ", f1[1], f2[1], f3[1]);
    	}
    }
    /*----------------------------------------函数*/
    signed main() {
    	n = read();
    	for(int i = 1; i < n; i++)
    	{
    		int x = read(), y = read();
    		add_edge(x, y); add_edge(y, x);
    	}
    	Cut::dfs1(1, 0); Cut::dfs2(1, 1); m = read();
    	while(m--) VT::solve(read());
    	return 0;
    }
    
    

    长链剖分

    单调队列

    1. (UVA1169 Robotruck)

    (w) 表示重量的前缀和 (dis) 表示每个点与前一个点距离的前缀和 (d) 表示一个点到原点的距离 有

    [f_i = min{f_j + d_i + d_{j + 1} + dis_i - dis_{j + 1}} ]

    单调队列优化即可

    最优解 (rank2) 第一太强了

    /*
      Time: 5.5
      Worker: Blank_space
      Source: UVA1169 Robotruck
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int T, n, f[B], q[B], l, r, c, d[B], dis[B], w[B], _x, _y;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    void work() {
    	memset(f, 63, sizeof f); w[0] = dis[0] = d[0] = _x = _y = 0;
    	c = read(); n = read(); f[0] = 0; l = r = 1; q[1] = 0;
    	for(int i = 1; i <= n; i++) 
    	{
    		int x = read(), y = read(); w[i] = read() + w[i - 1]; d[i] = x + y;
    		dis[i] = Abs(x - _x) + Abs(y - _y) + dis[i - 1];
    		_x = x; _y = y;
    	}
    	for(int i = 1; i <= n; i++)
    	{
    		while(l <= r && w[i] - w[q[l]] > c) l++;
    		f[i] = f[q[l]] + d[i] + d[q[l] + 1] + dis[i] - dis[q[l] + 1];
    		while(l <= r && f[q[r]] + d[q[r] + 1] - dis[q[r] + 1] >= f[i] + d[i + 1] - dis[i + 1]) r--;
    		q[++r] = i;
    	}
    	printf("%d
    ", f[n]);
    	if(T) puts("");
    }
    /*----------------------------------------函数*/
    int main() {
    	T = read(); while(T--) work();
    	return 0;
    }
    

    斜率优化

    1. (CF311B) (Cats) (Transport)

    式子符号反了 我无可救药

    比一般的斜率优化多了一维状态 预处理每只猫被带走时饲养员应出发的时间 (t) 对这东西排序
    (f_{i, j}) 表示第 (i) 个饲养员带走第 (j) 只猫的最小等待时间
    有:

    [f_{i, j} = min{f_{i - 1, k} + sum_{l = k + 1}^{j}left(t_j - t_l ight)} ]

    维护一个前缀和 再化一下式子 套上斜率优化就行了 我的式子推挂了所以不放了

    挂分小技巧:

    不开 (long) (long) 见祖宗

    代码:

    /*
      Time: 4.25
      Worker: Blank_space
      Source: CF311B Cats Transport
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define int long long
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, p, t[B], d[B], sum[B], q[B], f[110][B], l, r, ans;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    double K(int x, int y, int i) {return (f[i][x] - f[i][y] + sum[x] - sum[y]) * 1.0 / (x - y) * 1.0;}
    /*----------------------------------------函数*/
    signed main() {
    	n = read(); m = read(); p = read(); memset(f, 63, sizeof f); f[0][0] = 0;
    	for(int i = 2; i <= n; i++) d[i] = read() + d[i - 1];
    	for(int i = 1; i <= m; i++) {int x = read(); t[i] = read() - d[x];}
    	std::sort(t + 1, t + 1 + m);
    	for(int i = 1; i <= m; i++) sum[i] = sum[i - 1] + t[i];
    	for(int i = 1; i <= m; i++) f[1][i] = i * t[i] - sum[i]; ans = f[1][m];
    	for(int i = 2; i <= p; i++)
    	{
    		l = r = 1; q[r] = 0;
    		for(int j = 1; j <= m; j++)
    		{
    			while(l < r && K(q[l + 1], q[l], i - 1) <= t[j]) l++;
    			f[i][j] = f[i - 1][q[l]] + (j - q[l]) * t[j] - sum[j] + sum[q[l]];
    			while(l < r && K(q[r - 1], q[r], i - 1) >= K(q[r], j, i - 1)) r--;
    			q[++r] = j;
    		}
    		ans = Min(ans, f[i][m]);
    	}
    	printf("%lld", f[p][m]);
    	return 0;
    }
    
    

    2. (P4360) 锯木厂选址

    头一次知道还能这样 (DP) 我芝士匮乏

    直接记出总贡献 然后不断枚举两个点 尝试减去相应的贡献 取最小值 不知道算不算是一个倒序的 (DP)

    /*
      Time: 4.25
      Worker: Blank_space
      Source: P4360 [CEOI2004]锯木厂选址
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<cstring>
    #define int long long
    #define Abs(x) ((x) < 0 ? -(x) : (x))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, d[B << 1], w[B << 1], ans = INF, q[B << 1], l = 1, r = 1, tot;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    double K(int x, int y) {return (w[y] * d[y] - w[x] * d[x]) * 1.0 / (w[y] - w[x]) * 1.0;}
    /*----------------------------------------函数*/
    signed main() {
    	n = read(); q[1] = 0;
    	for(int i = 1; i <= n; i++) w[i] = read() + w[i - 1], d[i] = read(), tot += w[i] * d[i];
    	for(int i = n; i >= 1; i--) d[i] += d[i + 1];
    	for(int i = 1; i <= n; i++)
    	{
    		while(l < r && K(q[l], q[l + 1]) >= d[i]) l++;
    		ans = Min(ans, tot - w[q[l]] * d[q[l]] - (w[i] - w[q[l]]) * d[i]);
    		while(l < r && K(q[r - 1], q[r]) <= K(q[r], i)) r--;
    		q[++r] = i;
    	}
    	printf("%lld", ans);
    	return 0;
    }
    

    3. (P3648) 序列分割

    其实这是一个板子题 但是由于整个题解里面的式子都没有和我推的一样的 (其实有一个 但是那个题解太扯淡了) 不会打印路径 写挂了又调不出来 然后就搞了一晚上 重构代码...

    果然自己推 (dp)(dp) 是不现实的...

    爆零小技巧

    代码

    /*
      Time: 5.14
      Worker: Blank_space
      Source: P3648 [APIO2014]序列分割
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #define int long long
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, k, a[B], f[2][B], q[B], l = 1, r = 1, ans, g[210][B], id;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    double K(int t, int x, int y) {if(a[x] == a[y]) return -1e9; return (a[x] * a[x] - a[y] * a[y] - f[t & 1 ^ 1][x] + f[t & 1 ^ 1][y]) * 1.0 / (a[x] - a[y]) * 1.0;}
    /*----------------------------------------函数*/
    signed main() {
    	n = read(); k = read();
    	for(int i = 1; i <= n; i++) a[i] = read() + a[i - 1];
    	for(int t = 1; t <= k; t++, l = r = 0) for(int i = 1; i <= n; i++)
    	{
    		while(l < r && K(t, q[l], q[l + 1]) <= a[i]) l++;
    		f[t & 1][i] = f[t & 1 ^ 1][q[l]] + a[q[l]] * (a[i] - a[q[l]]); g[t][i] = q[l];
    		while(l < r && K(t, q[r - 1], q[r]) >= K(t, q[r], i)) r--;
    		q[++r] = i;
    	}
    	printf("%lld
    ", f[k & 1][n]);
    	for(int i = k, j = g[i][n]; i; i--, j = g[i][j]) printf("%lld ", j);
    	return 0;
    }
    
    

    期望 (dp)

    1. (CF865C Gotta Go Fast)

    我永远想不明白的期望题

    二分一个期望通过时间 期望 (dp) 进行检验

    状态 (f_{i, j}) 表示剩余 (i) 个关卡 剩余 (j) 的时间时期望通关的时间

    转移 (f_{i, j} = (f_{i + 1, j + a_i} + a_i) imes p_i / 100 + (f_{i + 1, j + b_i} + b_i) imes (100 - p_i) / 100)

    由于可以重来 对二分出的值取 (min)

    转移 (f_{i, j} = min{mid, (f_{i + 1, j + a_i} + a_i) imes p_i / 100 + (f_{i + 1, j + b_i} + b_i) imes (100 - p_i) / 100})

    爆零小技巧

    /*
      Time: 5.15
      Worker: Blank_space
      Source: CF865C Gotta Go Fast
    */
    /*--------------------------------------------*/
    #include<cstdio>
    #include<algorithm>
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    const int B = 1e5 + 7;
    const int C = 1e6 + 7;
    const int D = 1e7 + 7;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    /*------------------------------------常量定义*/
    int n, m, a[60], b[60], p[60];
    double f[60][5010];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    bool check(double x) {
    	for(int i = n - 1; ~i; i--)
    	{
    		for(int j = m + 1; j < 5000; j++) f[i + 1][j] = x;
    		for(int j = 0; j <= m; j++)
    			f[i][j] = std::min(x, (f[i + 1][j + a[i]] + a[i]) * p[i] * 1.0 / 100 + (f[i + 1][j + b[i]] + b[i]) * (100 - p[i]) * 1.0 / 100);
    	}
    	return f[0][0] < x;
    }
    /*----------------------------------------函数*/
    int main() {
    	n = read(); m = read();
    	for(int i = 0; i < n; i++) a[i] = read(), b[i] = read(), p[i] = read();
    	double l = 0, r = 1e10, mid = (l + r) / 2;
    	for(int i = 1; i <= 100; i++, mid = (l + r) / 2)
    		if(check(mid)) r = mid; else l = mid;
    	printf("%.10lf", l);
    	return 0;
    }
    
    

    2. (P4550) 收集邮票

    第一个认真 推的期望题

    (k) 次需要支付 (k) 元钱 若一共购买了 (x) 次 则需要 (sum_{i = 1}^x i = frac{x^2 + x}2)

    状态 (g_i) 表示已经买到了 (i) 种时 买全所有的还需要的期望次数 相应的 (f_i) 表示对应的二次方

    转移 (g_i = (g_i + 1)frac in + (g_{i + 1} + 1)frac {n - i}n)

    发现这个转移构成了环 将 (g_i) 弄到另一边 化简 得

    [g_i = frac n{n - i} + g_{i + 1} ]

    相应的 有 (f_i = (f_i + 2g_i + 1)frac in + (f_{i + 1} + 2g_{i + 1} + 1)frac {n - i}n)

    化简 得:

    [f_i = frac n{n - i} + frac{2ig_i}{n - i} + 2g_{i + 1} + f_{i + 1} ]

    代码

    /*
      Time: 5.15
      Worker: Blank_space
      Source: P4550 收集邮票
    */
    /*--------------------------------------------*/
    #include<cstdio>
    /*--------------------------------------头文件*/
    const int A = 1e4 + 7;
    /*------------------------------------常量定义*/
    int n;
    double g[A], f[A];
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    int main() {
    	n = read();
    	for(int i = n - 1; ~i; i--)
    		g[i] = 1.0 * n / (n - i) + g[i + 1], f[i] = 1.0 * n / (n - i) + 2.0 * i * g[i] / (n - i) + 2 * g[i + 1] + f[i + 1];
    	printf("%.2lf", (g[0] + f[0]) / 2);
    	return 0;
    }
    
    

    3. (P3802) 小魔女帕琪

    期望 (dp)

    我发现这种题就是式子化一张纸 代码一两行 遇到直接推式子...

    (n = sum_{i = 1}^7 a_i)

    考虑前七次触发的概率 为

    [frac {a_1}{n} imes frac {a_2}{n - 1} imes frac {a_3}{n - 2} imes frac {a_4}{n - 3} imes frac {a_5}{n - 4} imes frac {a_6}{n - 5} imes frac {a_7}{n - 6} = prod_{i = 1}^7 frac {a_i}{n - i + 1} ]

    由于 (a_i) 的顺序 前面在乘上一个 (7!)

    所以前七次能够触发的概率 设为 (p_1)

    [p_1 = 7! imes prod_{i = 1}^7 frac {a_i}{n - i + 1} ]

    考虑第八个继续触发的概率 其与第一个无关 若第一个随机到的为 (a_1) 那么有:

    [frac {a_1}n imes frac {a_1 - 1}{n - 1} imes frac {a_2}{n - 2} imes frac {a_3}{n - 3} imes frac {a_4}{n - 4} imes frac {a_5}{n - 5} imes frac {a_6}{n - 6} imes frac {a_7}{n - 7} = frac{a_1 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} ]

    第一个是随机的 所以概率 设为 (p_2) 为 :

    [egin{array} \ p_2 & = 7! imes left( frac{a_1 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} + frac{a_2 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} + frac{a_3 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} + frac{a_4 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} + frac{a_5 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} + frac{a_6 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} + frac{a_7 - 1}{n}prod_{i = 1}^7 frac{a_i}{n - i} ight)\ & = 7! imes frac{sum_{i = 1}^7a_i - 7}{n} imes prod_{i = 1}^7 frac{a_i}{n - i}\ & = 7! imes frac{n - 7}{n} imes prod_{i = 1}^7 frac{a_i}{n - i}\ & = 7! imes frac{n - 7}{n} imes frac {a_1}{n - 1} imes frac {a_2}{n - 2} imes frac {a_3}{n - 3} imes frac {a_4}{n - 4} imes frac {a_5}{n - 5} imes frac {a_6}{n - 6} imes frac {a_7}{n - 7}\ & = 7! imes prod_{i = 1}^7 frac {a_i}{n - i + 1} end{array} ]

    我们发现了什么

    [p_1 = p_2 ]

    这就好办了...

    下面就不赘述了

    代码

    /*
      Time: 5.15
      Worker: Blank_space
      Source: P3802 小魔女帕琪
    */
    /*--------------------------------------------*/
    #include<cstdio>
    /*--------------------------------------头文件*/
    int a[8], sum;
    double ans = 1;
    /*------------------------------------变量定义*/
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    /*----------------------------------------快读*/
    
    /*----------------------------------------函数*/
    int main() {
    	for(int i = 1; i <= 7; i++) a[i] = read(), sum += a[i];
    	for(int i = 1; i <= 6; i++) ans *= a[i] * 1.0 / (sum + 1 - i) * i;
    	printf("%.3lf", ans * a[7] * 7.0);
    	return 0;
    }
    
    
  • 相关阅读:
    年少时的"胡思乱想"
    daemon框架
    MVC框架,see again
    《Redis设计与实现》读书笔记
    小胖妞洗发水广告
    项目视图 Project Browser
    Unity 基础
    Unity手册-Unity概述
    rabbitmq 命令&& rabbitmq教程(一)
    C#动态方法调用 提高程序的扩展性
  • 原文地址:https://www.cnblogs.com/blank-space-/p/14699119.html
Copyright © 2011-2022 走看看