constint N = 20; int a[N][N], n, m, r, c; int rows[N], w[N]; int f[N][N], pre[N][N]; int ans = 2e9;
intcost(int x, int y){ int res = 0; for (int j = 1; j <= r; j++) res += abs(a[rows[j]][x] - a[rows[j]][y]); return res; }
voiddfs(int u, int cnt){ if (u > n) return; rows[cnt] = u; if (cnt < r) { for (int i = u+1; i <= n; i++) dfs(i, cnt+1); return; }
// 预处理列分值 memset(w, 0, sizeof w); for (int j = 1; j <= m; j++) { for (int i = 2; i <= r; i++) { w[j] += abs(a[rows[i]][j] - a[rows[i-1]][j]); } } // 选择 C 列 memset(f, 0x3f, sizeof f); f[0][0] = 0; for (int i = 1; i <= m; i++) { f[i][0] = 0, f[i][1] = w[i]; for (int j = 2; j <= c; j++) { for (int k = 1; k < i; k++) { f[i][j] = min(f[i][j], f[k][j-1] + cost(k, i) + w[i]); } } } for (int i = c; i <= m; i++) ans = min(ans, f[i][c]); }
intmain(){ scanf("%d%d%d%d", &n, &m, &r, &c); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]); for (int i = 1; i <= n; i++) dfs(i, 1);
printf("%d\n", ans); return0; }
[NOIP2005] 过河
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,⋯,L(其中 L 是桥的长度)。坐标为 0 的点表示桥的起点,坐标为 L 的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S 到 T 之间的任意正整数(包括 S,T)。当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。
constint N = 110, M = 11000; int L, S, T, n, p[N], stone[N]; int f[M], s[M];
intmain(){ scanf("%d%d%d%d", &L, &S, &T, &n); for (int i = 1; i <= n; i++) scanf("%d", &p[i]); // S = T 只能表达 S 的倍数, 无法保证可以表达出 72 以上的任何一个数 if (S == T) { int res = 0; for (int i = 1; i <= n; i++) { if (p[i] % S == 0) res++; } printf("%d\n", res); return0; } sort(p+1, p+1+n); int m = 0; for (int i = 1; i <= n; i++) { stone[i] = stone[i-1] + min(p[i] - p[i-1], 100); m += stone[i] - stone[i-1]; s[stone[i]] |= 1; } if (L - m > 10) L = m+10; memset(f, 0x3f, sizeof f); f[0] = 0; for (int i = 0; i < L; i++) { for (int j = i+S; j <= i+T; j++) f[j] = min(f[j], f[i]+s[j]); } int ans = 1e9; for (int i = L; i <= L+10; i++) ans = min(ans, f[i]); printf("%d\n", ans); return0; }
[NOIP2015] 子串
有两个仅包含小写英文字母的字符串 A 和 B。
现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一个新的字符串,请问有多少种方案可以使得这个新串与字符串 B 相等?答案对 1e9+7 取模。
constint N = 210, M = 810, base = 400; int n, m; int p[N], d[N]; int f[N][25][M], ans[25];
intmain(){ int C = 1; while (scanf("%d%d", &n, &m), n || m) { for (int i = 1; i <= n; i++) scanf("%d%d", &p[i], &d[i]); memset(f, -0x3f, sizeof f); for (int i = 0; i <= n; i++) f[i][0][base] = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { for (int k = 0; k < M; k++) { f[i][j][k] = f[i-1][j][k]; int t = k - (p[i] - d[i]); if (t < 0 || t >= M) continue; f[i][j][k] = max(f[i][j][k], f[i-1][j-1][t] + p[i] + d[i]); } } } int v = 0; while (f[n][m][base+v] < 0 && f[n][m][base-v] < 0) v++; if (f[n][m][base-v] > f[n][m][base+v]) v = -v; int cnt = 0, i = n, j = m, k = base+v; while (i) { // 没选 i if (f[i][j][k] == f[i-1][j][k]) i--; else { ans[cnt++] = i; k -= p[i] - d[i]; i--, j--; } } int sp = 0, sd = 0; for (int i = 0; i < cnt; i++) sp += p[ans[i]], sd += d[ans[i]]; printf("Jury #%d\n", C++); printf("Best jury has value %d for prosecution and value %d for defence:\n", sp, sd); for (int i = cnt-1; i >= 0; i--) printf(" %d", ans[i]); puts("\n"); } return0; }
[CSP-S2019] Emiya 家今天的饭
Emiya 是个擅长做菜的高中生,他共掌握 n 种烹饪方法,且会使用 m 种主要食材做菜。为了方便叙述,我们对烹饪方法从 1∼n 编号,对主要食材从 1∼m 编号。
typedeflonglong LL; constint N = 45, M = 4, P = 998244353; int n, m, f[N][N][N][N]; int a[N][M];
inlineintadd(int a, int b){ return (a+b) % P; }
inlineintmul(int a, int b){ return (LL)a*b % P; }
intmain(){ scanf("%d%d", &n, &m); // 考场上可以随便输出一个随机数, 这里防止消耗我 RP 就不加了 if (m > 3) { puts("-1"); return0; } for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]); f[0][0][0][0] = 1; for (int i = 1; i <= n; i++) { f[i][0][0][0] = 1; for (int x = 0; x <= i; x++) for (int y = 0; y <= i; y++) for (int z = 0; z <= i; z++) { f[i][x][y][z] = f[i-1][x][y][z]; if (x) f[i][x][y][z] = add(f[i][x][y][z], mul(f[i-1][x-1][y][z], a[i][1])); if (y) f[i][x][y][z] = add(f[i][x][y][z], mul(f[i-1][x][y-1][z], a[i][2])); if (z) f[i][x][y][z] = add(f[i][x][y][z], mul(f[i-1][x][y][z-1], a[i][3])); } } int res = 0; for (int x = 0; x <= n; x++) for (int y = 0; y <= n; y++) for (int z = 0; z <= n; z++) if (x+y+z > 0 && max({x, y, z}) <= (x+y+z) / 2) { res = add(res, f[n][x][y][z]); } printf("%d\n", res); return0; }
容斥+差值状态 100pts
如果只满足前两个条件,这个问题还是比较简单的,先做一遍 DP 求出来所有的方案。
状态表示 f(i,j):前 i 个烹饪方法做 j 道菜的方案数(种类)。
初始化:f(i,0)=1
状态转移:第 i 种烹饪方法一共可以做当前列之和种菜。
f(i,j)=f(i−1,j)+f(i−1,j−1)⋅(ai,1+ai,2+⋯+ai,m)
用到乘法原理。
这一步可以先对每列求和进行优化。
然后,枚举每个食材,对每个食材做一遍 DP,求出用 n 种烹饪方法做 i=1,2,⋯,n 个菜时第 k 种食材用了多于 ⌊i/2⌋ 的方案数。