ll dp[2][3]; ll n; ll a[MAXN]; voidsolve(){ n = read(); for (int i = 1; i <= n; i++) a[i] = read(); //空仓,0,要么前一天空仓,要么前两天卖出即前一天冷冻 //持仓, 1,要么前一天持仓,要么前一天空仓后当前买入 //冷冻,2,前一天持仓前一天卖出 dp[0][1] = -INF, dp[0][2] = -INF; //非法状态 dp[0][0] = 0; for (int i = 1; i <= n; i++) { int now = i & 1; int pre = 1 - now; dp[now][0] = max(dp[pre][0], dp[pre][2]); dp[now][1] = max(dp[pre][1], dp[pre][0] - a[i]); dp[now][2] = dp[pre][1] + a[i]; } printf("%lld\n", max(dp[n & 1][0], dp[n & 1][2])); }
**题目:**给定一个长度为 m 的字符串T和一个整数 n ,现需设计一个密码S满足 S 仅有长度为 n 的小写字母组成且 S 不包含子串 T 。问有几种方案。
只有密码的最大后缀子串为 m 时方案才不合法,也就是说最大后缀子串长度小于 m 时都是合法方案。定义 dp[i][j] 表示构造一个长度为 i 的密码,且后缀与模式串匹配的最大长度为 j 的方案数量。根据上面的结论,可以得出 ans=∑dp[n][j],0≤j<m 。可以证明这是不重不漏的。这时一共有 m+1 个状态,其中最后一个状态即匹配长度为 m 的状态为非法状态。
在 n×n 的棋盘上放 k 个国王。国王可攻击相邻的 8 个格子,求使得这 k 棋子无法互相攻击的方案总数。
用长度为 n 的二进制数来表示一行的状态。这样当 (st>>i)=1 时,即表示当前这位上存在棋子,否则不存在。这样就可以预处理出单独一行的所有合法状态和两行之间的所有合法状态。定义 dp[i][k][j] 为第 i 行,放置了 k 个国王,当前行状态为 j 时的方案数量。初始化 dp[0][0][0]=1
int n, m; ll dp[2][105][(1 << MAXN) + 5]; int cnt[(1 << MAXN)]; vector<int> v; //存储当前行的合法状态 vector<int> g[(1 << MAXN) + 5]; //存储行与行之间的合法状态 booljudge(int st){ //行内判断合法状态 if (st & st << 1) returnfalse; if (st & st >> 1) returnfalse; returntrue; }
boolwork(int now, int pre){ //行与行之间判断合法状态 if (now & pre) returnfalse; if (now & pre >> 1) returnfalse; if (now & pre << 1) returnfalse; returntrue; }
intsum(int st){ int ans = 0; for (int i = 0; i < n; i++) { int u = st >> i & 1; ans += u; } return ans; }
voidsolve(){ n = read(), m = read(); vector<int> v; for (int st = 0; st < (1 << n); st++) if (judge(st)) v.pb(st), cnt[st] = sum(st); for (int now : v) for (int pre : v) if (work(now, pre)) g[now].pb(pre); dp[0][0][0] = 1; for (int i = 1; i <= n; i++) for (int k = 0; k <= m; k++) for (int now : v) { dp[i & 1][k][now] = 0; //先清零因为是+=,滚动数组之前的结果有影响 for (int pre : g[now]) if (cnt[now] <= k) dp[i & 1][k][now] += dp[(i - 1) & 1][k - cnt[now]][pre]; } ll ans = 0; for (int st : v) ans += dp[n & 1][m][st]; printf("%lld\n", ans); }
题目: 给定一个 n×m 的矩阵,矩阵中 H 表示不能放置棋子,标 P 表示可以放置棋子。棋子的攻击方向为上下左右,距离两个单位。求出最大的棋子放置数量,使得棋子之间不会互相攻击。
加入了图的限制。不过可以用类似的方式,把矩阵每一层的状态也用二进制来压缩存储,注意:用0表示该位置能放棋子,1表示不能放 。这样只需要把当前这一层的状态 st 和矩阵这一层的状态做与运算。如果结果为 0 ,表示合法;若为 1 ,说明放置棋子的位置与不能放置棋子的位置发生重叠,该状态非法。然后可以发现,与之前的 work 函数所表示的含义是一样的,于是乎就可以共用了。
char s[105][15]; int a[105]; int n, m; vector<int> v; vector<int> g[(1 << 10) + 5]; int cnt[(1 << 10) + 5]; ll dp[2][(1 << 10) + 5][(1 << 10) + 5]; booljudge(int st){ if (st & (st >> 1) || st & (st >> 2)) returnfalse; if (st & (st << 1) || st & (st << 2)) returnfalse; returntrue; }
boolwork(int now, int pre){ if (now & pre) returnfalse; returntrue; }
intsum(int st){ int ans = 0; for (int i = 0; i < m; i++) { int u = st >> i & 1; ans += u; } return ans; }
voidsolve(){ n = read(), m = read(); for (int i = 1; i <= n; i++) scanf("%s", s[i]); for (int i = 1; i <= n; i++) { int ans = 0; for (int j = 0; j < m; j++) { if (s[i][j] == 'P') //山地 {} else//平原 ans += (1 << j); } a[i] = ans; } for (int st = 0; st < (1 << m); st++) if (judge(st)) v.pb(st), cnt[st] = sum(st); for (int now : v) for (int pre : v) if (work(now, pre)) g[now].pb(pre); for (int i = 1; i <= n; i++) for (int now : v) for (int PRE : g[now]) { dp[i & 1][now][PRE] = 0; //清空滚动数组之前的结果 if (work(now, a[i])) //当前行合法 for (int pre : g[PRE]) //枚举上上行 if (work(now, pre)) //若上上行和当前和不会互相攻击 dp[i & 1][now][PRE] = max(dp[i & 1][now][PRE], dp[(i - 1) & 1][PRE][pre] + cnt[now]); } ll ans = 0; for (int now : v) for (int pre : g[now]) ans = max(ans, dp[n & 1][now][pre]); printf("%lld\n", ans); }
上面只管了当前这一行与图的合法关系。因为之前行若与图之间不合法的话,最大值一定为 0 (因为有个 if 来判断),对答案无影响。如果是算方案数类型的题目也没影响,因为一开始会对所有的状态清零,若某一状态与图之间的关系非法,那么该状态的方案数也一定为 0 ,对答案也没有影响。代码写起来长这样:
1 2 3 4 5 6 7 8
dp[0][0] = 1; for (int i = 1; i <= n; i++) for (int now : v) { dp[i & 1][now] = 0; if (work(now, a[i])) for (int pre : g[now]) dp[i & 1][now] = (dp[i & 1][now] + dp[(i - 1) & 1][pre]) % mod; }
更加易懂(但是慢)的代码:
1 2 3 4 5 6 7 8 9 10 11
for (int i = 1; i <= n; i++) for (int j = 0; j < state.size(); j++) for (int k = 0; k < state.size(); k++) for (int u = 0; u < state.size(); u++) { int a = state[j], b = state[k], c = state[u]; if (a & b | a & c | b & c) //能互相攻击 continue; if (g[i] & b | g[i - 1] & a) //在不该放置棋子的地方放了棋子 continue; f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]); }
集合类
题意: 愤怒的小鸟模型。给出 n 个猪的坐标,问最少几条抛物线能把猪全部打死(抛物线开口向下,且一定从坐标原点开始)
根据简单数学知识,在本题中,两头猪确定一条抛物线,所以最多有 n2 条抛物线。
预处理 seg[i][j] 数组,表示编号为 i 的猪和编号为 j 的猪所在的抛物线。属性表示为这个抛物线可消灭的猪的编号,二进制表示。如100111,表示 1,2,3,6 的猪能被当前这条抛物线杀死。
状态表示 dp[i] ,i 这个状态(二进制表示)下最少要用多少条抛物线击杀所有猪。属性为最小值。
状态计算:找到 i 状态下没有被消灭的猪的其中一个编号 x ,枚举可消灭它的抛物线 seg[x][j] 并更新状态。
int seg[MAXN][MAXN]; //所有抛物线杀死猪的情况,1表示被杀死 pdd node[MAXN]; ll dp[1 << MAXN]; int n, m;
intcmp(double a, double b){ if (fabs(a - b) < 1e-8) return0; //相同 if (a > b) return1; //a大于b if (a < b) return-1; //a小于b }
voidsolve(){ n = read(), m = read(); for (int i = 0; i < n; i++) scanf("%lf%lf", &node[i].first, &node[i].second); memset(seg, 0, sizeof seg); for (int i = 0; i < n; i++) { seg[i][i] = 1 << i; //一条直线 for (int j = 0; j < n; j++) { //两个点确定一条抛物线 double x = node[i].first, y = node[i].second; double xx = node[j].first, yy = node[j].second; double a = (y / x - yy / xx) / (x - xx); double b = (y / x - a * x); if (cmp(a, 0.0) != -1) continue; //抛物线开口向上,舍去 for (int k = 0; k < n; k++) { double u = node[k].first, v = node[k].second; if (cmp(a * u * u + b * u, v) == 0) seg[i][j] += 1 << k; //能够杀死这头猪 }
} } memset(dp, 0x3f, sizeof dp); dp[0] = 0; for (int st = 0; st < (1 << n) - 1; st++) { int pos = -1; for (int i = 0; i < n; i++) if (!(st >> i & 1)) pos = i; for (int i = 0; i < n; i++) { int nxt = seg[pos][i] | st; dp[nxt] = min(dp[nxt], dp[st] + 1); } } printf("%lld\n", dp[(1 << n) - 1]); }
ll dfs(int now, int pre){ ll mx = 0; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; int val = edge[i].val; if (to == pre) continue; ll tmp = val + dfs(to, now); mx = max(mx, tmp); if (tmp > ans1) { ans2 = ans1; ans1 = tmp; } elseif (tmp <= ans1 && tmp > ans2) { ans2 = tmp; } ans = max(ans, ans1 + ans2); } return mx; }
voidsolve(){ n = read(); for (int i = 1; i < n ; i++) { ll u = read(), v = read(), w = read(); add_edge(u, v, w); add_edge(v, u ,w); } dfs(1, -1); printf("%lld\n", ans); }
ll depth[MAXN], mxdep; ll dp[MAXN]; //i节点连向父亲的边的长度 ll ans = 0; voiddfs_pre(int now, int pre){ for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; ll val = edge[i].val; if (to == pre) continue; depth[to] = depth[now] + val; mxdep = max(mxdep, depth[to]); dfs_pre(to, now); } }
voiddfs(int now, int pre){ int cnt = 0; ll mn = INF; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; ll val = edge[i].val; if (to == pre) continue; dfs(to, now); cnt++; mn = min(mn, dp[to]); } if (!cnt) { dp[now] = mxdep - depth[now]; ans += dp[now]; return; } if (now != rt) { dp[now] = mn; ans -= (cnt - 1) * mn; } }
voidsolve(){ n = read(), rt = read(); for (int i = 1; i < n; i++) { int u, v; ll val; u = read(), v = read(), val = read(); add(u, v, val); add(v, u, val); } dfs_pre(rt, -1); dfs(rt, -1); printf("%lld\n", ans); }
int n; int down1[MAXN], down2[MAXN], up[MAXN]; int son1[MAXN], son2[MAXN];
voiddfs_down(int now, int pre){ for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; int val = edge[i].val; if (to == pre) continue; dfs_down(to, now); if (down1[to] + val > down1[now]) { down2[now] = down1[now], son2[now] = son1[now]; down1[now] = down1[to] + val, son1[now] = to; } elseif (down1[to] + val > down2[now]) { down2[now] = down1[to] + val, son2[now] = to; } } }
voiddfs_up(int now, int pre){ for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; int val = edge[i].val; if (to == pre) continue; if (son1[now] == to) { up[to] = val + max(up[now], down2[now]); } else { up[to] = val + max(up[now], down1[now]); } dfs_up(to, now); //最后再往下走 } }
voidsolve(){ n = read(); for (int i = 1; i < n; i++) { int u = read(), v = read(), w = read(); add(u, v, w); add(v, u, w); } dfs_down(1, -1); dfs_up(1, -1); int ans = INF; for (int i = 1; i <= n; i++) ans = min(ans, max(down1[i], up[i])); printf("%d\n", ans); }
ll a[MAXN]; ll siz[MAXN], dp[MAXN], depth[MAXN]; ll n, N;
voiddfs_down(int now, int pre){ siz[now] = a[now]; dp[now] = depth[now] * a[now]; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; ll val = edge[i].val; if (to == pre) continue; depth[to] = depth[now] + val; dfs_down(to, now); siz[now] += siz[to]; dp[now] += dp[to]; } }
voiddfs_up(int now, int pre){ for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; ll val = edge[i].val; if (to == pre) continue; dp[to] = dp[now] - siz[to] * val + (N - siz[to]) * val; dfs_up(to, now); } }
voidsolve(){ n = read(); for (int i = 1; i <= n; i++) { a[i] = read(); N += a[i]; } for (int i = 1; i < n; i++) { int u = read(), v = read(); ll w = read(); add(u, v, w); add(v, u, w); } dfs_down(1, -1); dfs_up(1, -1); ll ans = INF; for (int i = 1; i <= n; i++) { if (dp[i] < ans) { ans = dp[i]; } } printf("%lld\n", ans); }
题目: 给出一颗 n 个点的树,点带权,对于每个节点求出距离它不超过 k 的所有节点权值和 mi
k≤20
设 down[i][j] 表示从 i 点向下 j 的范围内由多少牛,dp[i][j] 表示 i 点的 j 范围内有多少牛。
ll n, k; ll a[MAXN]; ll dp[MAXN][25]; ll down[MAXN][25];
voiddfs_down(int now, int pre){ for (int i = 0; i <= k; i++) down[now][i] = a[now]; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (to == pre) continue; dfs_down(to, now); for (int j = 1; j <= k; j++) down[now][j] += down[to][j - 1]; } }
voiddfs_up(int now, int pre){ for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (to == pre) continue; dp[to][1] += down[now][0]; for (int j = 2; j <= k; j++) dp[to][j] += dp[now][j - 1] - down[to][j - 2]; //此时now以完成更新,用父亲来更新儿子 dfs_up(to, now); } }
voidsolve(){ n = read(), k = read(); for (int i = 1; i < n; i++) { int u = read(), v = read(); add(u, v); add(v, u); } for (int i = 1; i <= n; i++) a[i] = read(); dfs_down(1, -1); for (int i = 1; i <= n; i++) for (int j = 0; j <= k; j++) dp[i][j] = down[i][j]; dfs_up(1, -1); for (int i = 1; i <= n; i++) printf("%lld\n", dp[i][k]); }
voidinit(){ tot = 0; for (int i = 1; i <= n; i++) { head[i] = 0; } memset(dp, 0x3f, sizeof dp); }
voiddfs(int now, int pre){ dp[0][now] = 0, dp[1][now] = 1; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (to == pre) continue; dfs(to, now); dp[0][now] += dp[1][to]; dp[1][now] += min(dp[0][to], dp[1][to]); } }
voidsolve(){ for (int i = 1; i <= n; i++) { int u, k; scanf("%d:(%d)", &u, &k); u++; while (k--) { int v = read(); v++; add(u, v); add(v, u); } } dfs(1, -1); printf("%d\n", min(dp[1][1], dp[0][1])); }
int n; ll a[MAXN]; ll dp[3][MAXN]; //0表示被父亲观察,1表示被自己观察,2表示被儿子观察 //0的话儿子要么被自己观察,要么被自己的儿子观察 //1的话儿子被自己,父亲,儿子观察 //2的话儿子被自己观察的状态中找一种最小的方案,其余的儿子和0同理
voiddfs(int now, int pre){ dp[0][now] = 0, dp[1][now] = a[now], dp[2][now] = INF; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (to == pre) continue; dfs(to, now); dp[0][now] += min(dp[1][to], dp[2][to]); dp[1][now] += min({dp[0][to], dp[1][to], dp[2][to]}); } for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (to == pre) continue; dp[2][now] = min(dp[2][now], dp[0][now] - min(dp[1][to], dp[2][to]) + dp[1][to]); } }
voidsolve(){ n = read(); for (int i = 1; i <= n; i++) { int k, u; u = read(), a[u] = read(), k = read(); while (k--) { int v = read(); add(u, v); add(v, u); } } dfs(1, -1); printf("%lld\n", min(dp[1][1], dp[2][1])); }
int l, r, k, b; int a[MAXN], al; int dp[MAXN][MAXN];
intdfs(int pos, int cnt, int limit){ //枚举完所有数,看1出现的次数是否等于k if (!pos) return cnt == k; //当前限制以解除,返回记忆化结果 if (!limit && ~dp[pos][cnt]) return dp[pos][cnt]; //这个状态还没出现过,往下搜 int ans = 0; int up = limit ? a[pos] : 1; for (int i = 0; i <= up; i++) { if (cnt + i > k) continue; if (i > 1) continue; ans += dfs(pos - 1, cnt + i, limit && i == up); } return limit ? ans : dp[pos][cnt] = ans; }
intcalc(int x){ al = 0; memset(dp, -1, sizeof dp); while (x) { a[++al] = x % b; x /= b; } returndfs(al, 0, 1); }
voidsolve(){ l = read(), r = read(), k = read(), b = read(); printf("%d\n", calc(r) - calc(l - 1)); }
int l, r; int a[MAXN], al; int dp[MAXN][MAXN]; //前i位num出现过j次的方案数
intdfs(int pos, int cnt, int num, int lead, int limit){ if (!pos) { if (!num && lead) return1; //所有位上都是0 else return cnt; //返回num出现过的次数 } if (!limit && !lead && ~dp[pos][cnt]) return dp[pos][cnt]; int ans = 0; int up = limit ? a[pos] : 9; for (int i = 0; i <= up; i++) { int nxt; if (i == num) { //如果i是要找的那个数 if (!num) //要找的数是0,那么前导零的限制必须已解除 nxt = cnt + !lead; else nxt = cnt + 1; } else nxt = cnt; ans += dfs(pos - 1, nxt, num, lead && !i, limit && i == up); } return limit ? ans : (lead ? ans : dp[pos][cnt] = ans); }
intcalc(int n, int x){ memset(dp, -1, sizeof dp); al = 0; while (n) { a[++al] = n % 10; n /= 10; } returndfs(al, 0, x, 1, 1); }
voidsolve(){ for (int i = 0; i <= 9; i++) { printf("%d ", calc(r, i) - calc(l - 1, i)); } puts(""); }
intmain(){ while (scanf("%d%d", &l, &r), l || r) { if (l > r) swap(l, r); solve(); } return0; }
题目: 找出给定 [L,R] 区间内相邻数位之差大于 2 的数字个数。
要考虑前导零。定义状态 dp[i][j] 为 解除限制后 第 i 位数字为 j 时的方案数,本题方案数对应合法数字的个数。
int l, r; int a[MAXN], al; int dp[MAXN][MAXN]; //第i位数为j时的方案数
intdfs(int pos, int pre, int lead, int limit){ if (!pos) { return1; } if (!limit && !lead && ~dp[pos][pre]) return dp[pos][pre]; int ans = 0; int up = limit ? a[pos] : 9; for (int i = 0; i <= up; i++) { if (abs(i - pre) < 2) continue; if (lead && !i) ans += dfs(pos - 1, -2, 1, limit && i == up); else ans += dfs(pos - 1, i, 0, limit && i == up); } return limit ? ans : (lead ? ans : dp[pos][pre] = ans); }
intcalc(int x){ memset(dp, -1, sizeof dp); al = 0; while (x) { a[++al] = x % 10; x /= 10; } returndfs(al, -2, 1, 1); }
voidsolve(){ l = read(), r = read(); printf("%d\n", calc(r) - calc(l - 1)); }
题目: 找出 [L,R] 范围内从左到右数位非下降的数字的个数
不用考虑前导零,记录前驱就行。定义状态 dp[i][j] 为 解除限制后 第 i 位数字为 j 时的方案数,本题方案数对应数字的个数
intdfs(int pos, int pre, int limit){ if (!pos) { return1; } if (!limit && ~dp[pos][pre]) return dp[pos][pre]; int ans = 0; int up = limit ? a[pos] : 9; for (int i = 0; i <= up; i++) { if (i < pre) continue; ans += dfs(pos - 1, i, limit && i == up); } return limit ? ans : dp[pos][pre] = ans; }
intcalc(int x){ memset(dp, -1, sizeof dp); al = 0; while (x) { a[++al] = x % 10; x /= 10; } returndfs(al, -1, 1); }
int l, r, mod; int a[MAXN], al; int dp[MAXN][105];
intdfs(int pos, int sum, int limit){ if (!pos) { if (sum % mod == 0) return1; return0; } if (!limit && ~dp[pos][sum]) return dp[pos][sum]; int ans = 0; int up = limit ? a[pos] : 9; for (int i = 0; i <= up; i++) { ans += dfs(pos - 1, sum + i, limit && i == up); } return limit ? ans : dp[pos][sum] = ans; }
intcalc(int x){ al = 0; memset(dp, -1, sizeof dp); while (x) { a[++al] = x % 10; x /= 10; } returndfs(al, 0, 1); }
ll dfs(int pos, int cnt, int limit){ if (!pos) { returnmax(1ll, (ll)cnt); } if (!limit && ~dp[pos][cnt]) return dp[pos][cnt]; ll ans = 1; int up = limit ? a[pos] : 1; for (int i = 0; i <= up; i++) { int t = cnt; if (i) t++; ans = ans * dfs(pos - 1, t, limit && i == up) % mod; } return limit ? ans : dp[pos][cnt] = ans; }
ll calc(ll x){ memset(dp, -1, sizeof dp); al = 0; while (x) { a[++al] = x % 2; x /= 2; } returndfs(al, 0, 1); }
ll n, m; vector<pii> g[MAXN]; double dp[MAXN]; //以i作为起点,到达各个终点的路径期望之和
doubledfs(int now){ if (dp[now] >= 0) { //已经搜索过了 return dp[now]; } int len = g[now].size(); dp[now] = 0.0; for (int i = 0; i < len; i++) { int to = g[now][i].first; double val = g[now][i].second + 0.0; dp[now] += (val + dfs(to)) / (len + 0.0); } return dp[now]; }
voidsolve(){ n = read(), m = read(); memset(dp, ~0x3f, sizeof dp); for (int i = 1; i <= m; i++) { int u, v, w; u = read(), v = read(), w = read(); g[u].pb(v, w); } dfs(1); printf("%.2lf\n", dp[1]); }