动态规划做题笔记
P2606 [ZJOI2010] 排列计数
求有多少
的排列 满足 ,对 取模。
, , 是一个质数。
观察发现
有多少种给
个节点的完全二叉树分配权值 的方案,使得每个父亲的权值都小于左右儿子的权值。(原问题)
我们可以先将这
然后考虑 DP。设
考虑计算
显然我们会改变
而两棵子树内部的答案分别为
最后输出
int fac[N], inv[N], sz[N];
int dfs(int u) {
if (u > n) return 0;
if (u * 2 > n) return 1;
int l = u << 1, r = u << 1 | 1;
return 1 + (sz[l] = dfs(l)) + (sz[r] = dfs(r));
}
int dp(int u) {
if (u > n) return 1;
if (u * 2 > n) return 1;
int l = u << 1, r = u << 1 | 1;
return (ll)dp(l) * dp(r) % P * fac[sz[l] + sz[r]] % P * inv[sz[l]] % P * inv[sz[r]] % P;
}
int fpm(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (ll)res * a % P;
b >>= 1, a = (ll)a * a % P;
}
return res;
}
void Luogu_UID_748509() {
fin >> n >> P;
if (n == 4 && P == 2) puts("1");
else if (n == 7 && P == 3) puts("2");
else {
fac[0] = inv[0] = 1;
for (int i = 1; i <= n; ++ i ) {
fac[i] = (ll)fac[i - 1] * i % P;
inv[i] = fpm(fac[i], P - 2);
}
sz[1] = dfs(1);
fout << dp(1);
}
}
P3146 [USACO16OPEN] 248 G
- 给定一个序列,每次可以将两个相邻且相同的数合并成一个数,合并结果为原数加一。求最后能得到的最大数字。
, 。
最暴力的,设状态
由于
这样是可以通过的。
可以发现,如果一个区间
所以这个三维状态显得很愚蠢。我们重新设
int n, a[N];
int f[N][N];
void Luogu_UID_748509() {
fin >> n;
for (int i = 1; i <= n; ++ i ) {
fin >> a[i];
f[i][i] = a[i];
}
for (int len = 2; len <= n; ++ len )
for (int l = 1; l + len - 1 <= n; ++ l ) {
int r = l + len - 1;
f[l][r] = -1;
for (int k = l; k < r; ++ k )
if (f[l][k] != -1 && f[l][k] == f[k + 1][r])
f[l][r] = f[l][k] + 1;
}
int res = 0;
for (int l = 1; l <= n; ++ l )
for (int r = l; r <= n; ++ r )
res = max(res, f[l][r]);
fout << res;
}
P3147 [USACO16OPEN] 262144 P
- 题意同上。
, 。
仍然是区间 DP。但是显然状态不能设成
同时仍然可以发现,对于两个有着相同左端点和不同右端点的区间
我们重新设状态。考虑将其中一维放在状态之外。具体的,设状态
转移
int n, a[N];
int f[N][M];
void Luogu_UID_748509() {
fin >> n;
for (int i = 1; i <= n; ++ i ) {
fin >> a[i];
f[i][a[i]] = i;
}
int res = 0;
for (int j = 2; j < M; ++ j ) {
for (int i = 1; i <= n; ++ i ) {
if (f[i][j - 1]) f[i][j] = f[f[i][j - 1] + 1][j - 1];
if (f[i][j]) res = max(res, j);
}
}
fout << res << '\n';
return;
}
P2051 [AHOI2009] 中国象棋
- 求在
的棋盘上棋子,且不存在某一行或某一列有大于两个棋子的方案数。 。
设状态
接下来枚举第
为了方便可以写成刷表。
int n, m;
int f[N][N][N];
void Luogu_UID_748509() {
fin >> n >> m;
f[0][m][0] = 1;
int res = 0;
for (int i = 0; i <= n; ++ i )
for (int a = 0; a <= m; ++ a )
for (int b = 0; a + b <= m; ++ b ) if (f[i][a][b]) {
int c = m - a - b;
(f[i + 1][a][b] += f[i][a][b]) %= P;
if (a - 1 >= 0) (f[i + 1][a - 1][b + 1] += f[i][a][b] * a) %= P;
if (b - 1 >= 0) (f[i + 1][a][b - 1] += f[i][a][b] * b) %= P;
if (a - 2 >= 0) (f[i + 1][a - 2][b + 2] += f[i][a][b] * a * (a - 1) / 2) %= P;
if (a - 1 >= 0) (f[i + 1][a - 1][b] += f[i][a][b] * a * b) %= P;
if (b - 2 >= 0) (f[i + 1][a][b - 2] += f[i][a][b] * b * (b - 1) / 2) %= P;
if (n == i) res = (res + f[i][a][b]) % P;
}
fout << res;
}
P4805 [CCC2016] 合并饭团
给定一个序列,有如下操作:
- 选择两个相邻且相等的数字,将其合并为两个数的和。
- 选择三个相邻且左右两个相等的数字,将其合并为三个数的和。.
求最后能得到的最大数字。
。
不难发现如果一个区间能合并成一个数,那么这个数一定是这个区间的和。
所以可以设 bool 状态
- 第一种操作:
。 - 第二种操作:
。
直接转移是
我们注意到对于第二种操作的
所以 two-pointer 即可。
int n, a[N], sum[N];
bool f[N][N];
void Luogu_UID_748509() {
fin >> n;
int res = 0;
for (int i = 1; i <= n; ++ i ) {
fin >> a[i];
sum[i] = sum[i - 1] + a[i];
f[i][i] = true;
res = max(res, a[i]);
}
for (register int len = 2; len <= n; ++ len )
for (register int l = 1; l + len - 1 <= n; ++ l ) {
register int r = l + len - 1;
int p = r - 1;
for (register int k = l; k < r; ++ k ) {
f[l][r] |= f[l][k] && f[k + 1][r] && sum[k] - sum[l - 1] == sum[r] - sum[k];
while (k < p && sum[k] - sum[l - 1] > sum[r] - sum[p]) -- p;
if (k < p && sum[k] - sum[l - 1] == sum[r] - sum[p]) f[l][r] |= f[l][k] && f[k + 1][p] && f[p + 1][r];
}
if (f[l][r]) res = max(res, sum[r] - sum[l - 1]);
}
fout << res;
}
P4290 [HAOI2008] 玩具取名
- 给定一个由字母
组成的字符串和若干个变化规则,表示可以将相邻两个字母合并成一个字母。求这个字符串可以合并为哪些独个字母。 。
设 bool 状态
转移枚举断点
map<char, int> mp{{'W', 0}, {'I', 1}, {'N', 2}, {'G', 3}};
string pm = "WING";
int m = 4, cnt[4];
map<pair<int, int>, vector<int> > pp;
bool f[N][N][4];
char s[N];
int n;
void Luogu_UID_748509() {
for (int i = 0; i < m; ++ i ) fin >> cnt[i];
for (int i = 0; i < m; ++ i ) {
while (cnt[i] -- ) {
char a, b; cin >> a >> b;
pp[{mp[a], mp[b]}].push_back(i);
}
}
scanf("%s", s + 1);
n = strlen(s + 1);
for (int i = 1; i <= n; ++ i ) f[i][i][mp[s[i]]] = 1;
for (int len = 2; len <= n; ++ len )
for (int l = 1; l + len - 1 <= n; ++ l ) {
int r = l + len - 1;
for (int k = l; k < r; ++ k )
for (int i = 0; i < 4; ++ i )
if (f[l][k][i])
for (int j = 0; j < 4; ++ j )
if (f[k + 1][r][j])
for (int c : pp[{i, j}])
f[l][r][c] = 1;
}
bool flg = false;
for (int i = 0; i < 4; ++ i )
if (f[1][n][i])
putchar(pm[i]),
flg = true;
if (!flg) puts("The name is wrong!");
}
P4170 [CQOI2007] 涂色
- 有
个位置,最初均没有颜色。每次操作可以选择一个区间并覆盖同一种颜色。求最小操作次数使得与目标状态相同。 。
设状态
观察发现,如果我们想将一个区间
同时,对于区间的左/右断点,显然如果将其染色大于
所以我们可以在第一步就将整个区间涂成左端点的颜色。
此时,若左右端点颜色相同,我们可以染色区间
否则,枚举断点
int n;
char s[N];
int f[N][N];
void Luogu_UID_748509() {
scanf("%s", s + 1);
n = strlen(s + 1);
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; ++ i ) {
cin >> s[i];
f[i][i] = 1;
}
for (int len = 2; len <= n; ++ len ) {
for (int l = 1; l + len - 1 <= n; ++ l ) {
int r = l + len - 1;
if (s[l] == s[r]) f[l][r] = min(f[l][r - 1], f[l + 1][r]);
else {
for (int k = l; k < r; ++ k ) f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
}
}
}
fout << f[1][n];
}
LOJ P507 接竹竿
有
张牌排成一排,每张牌有属性 。保证 。 每次操作选择两张牌
满足 ,删除 中的所有牌,并获得 的收益。 求最大的收益。
。
设状态
转移枚举第
直接做是
可以把与
然后这个就很好维护了。我们用桶维护每个
int n, k, res, c[N], v[N];
ll f[N], sum[N];
map<int, ll> mp;
void Luogu_UID_748509() {
fin >> n >> k;
for (int i = 1; i <= n; ++ i ) fin >> c[i];
for (int i = 1; i <= n; ++ i ) fin >> v[i], sum[i] = sum[i - 1] + v[i];
for (int i = 1; i <= n; ++ i ) {
f[i] = f[i - 1];
if (mp.count(c[i])) {
f[i] = max(f[i], mp[c[i]] + sum[i]);
mp[c[i]] = max(mp[c[i]], f[i - 1] - sum[i - 1]);
}
else {
mp[c[i]] = f[i - 1] - sum[i - 1];
}
res = max(res, f[i]);
}
fout << res;
}
P4342 [IOI1998] Polygon
有一个
个顶点 条边的环,顶点上有数字,边上有 两种运算符号。 首先删掉一条边,然后每次选择一条连接
的边,用边上的运算符计算 和 得到的结果来替换这两个顶点。 求最后元素的最大值。
。
显然区间 DP。首先倍长破环为链。
设状态
这样做是不正确的。注意到两个负数相乘结果为正数,所以再维护
复杂度
int n;
bool op[N];
int a[N];
int f[N][N], g[N][N];
void Luogu_UID_748509() {
fin >> n;
for (int i = 1; i <= n; ++ i ) {
char c;
cin >> c >> a[i];
op[i] = c == 'x';
a[i + n] = a[i];
op[i + n] = op[i];
}
for (int i = 1; i <= n * 2; ++ i ) {
f[i][i] = g[i][i] = a[i];
}
for (int len = 2; len <= n; ++ len ) {
for (int l = 1; l + len - 1 <= n * 2; ++ l ) {
int r = l + len - 1;
f[l][r] = -1e9, g[l][r] = 1e9;
for (int k = l; k < r; ++ k ) {
vector<int> v;
if (op[k + 1]) v = {f[l][k] * f[k + 1][r], f[l][k] * g[k + 1][r], g[l][k] * f[k + 1][r], g[l][k] * g[k + 1][r]};
else v = {f[l][k] + f[k + 1][r], f[l][k] + g[k + 1][r], g[l][k] + f[k + 1][r], g[l][k] + g[k + 1][r]};
for (int i : v) {
f[l][r] = max(f[l][r], i);
g[l][r] = min(g[l][r], i);
}
}
}
}
int res = -1e9;
for (int l = 1, r = n; l <= n; ++ l, ++ r )
res = max(res, f[l][r]);
fout << res << '\n';
for (int l = 1, r = n; l <= n; ++ l, ++ r )
if (f[l][r] == res)
fout << l << ' ';
}
P4933 大师
- 给定一个长度为
的序列。求有多少个子序列是等差数列。 - 设
为序列最大值。 , 。
设状态
转移枚举倒数第二个元素
复杂度是
可以发现如果确定了
时间复杂度
int n, a[N];
int f[N][M * 2];
int res;
int fpm(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (ll)res * a % P;
b >>= 1, a = (ll)a * a % P;
}
return res;
}
int mp[M * 5];
void Luogu_UID_748509() {
fin >> n;
int mx = -1e9, mn = 1e9;
for (int i = 1; i <= n; ++ i ) fin >> a[i], mx = max(mx, a[i]), mn = min(mn, a[i]);
register int res = n, m = mx - mn + 1;
for (int j = -m; j <= m; ++ j ) {
memset(mp, 0, sizeof mp);
for (int i = 1; i <= n; ++ i ) {
if (a[i] >= j) (f[i][j + M] = mp[a[i] - j]) %= P;
(mp[a[i]] += f[i][j + M] + 1) %= P;
res = (res + f[i][j + M]) % P;
}
}
fout << res;
return;
}
P5662 [CSP-J2019] 纪念品
小伟突然获得一种超能力,他知道未来
天 种纪念品每天的价格。某个纪念品的价格是指购买一个该纪念品所需的金币数量,以及卖出一个该纪念品换回的金币数量。 每天,小伟可以进行以下两种交易无限次:
- 任选一个纪念品,若手上有足够金币,以当日价格购买该纪念品;
- 卖出持有的任意一个纪念品,以当日价格换回金币。
每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,一直持有纪念品也是可以的。
天之后,小伟的超能力消失。因此他一定会在第 天卖出所有纪念品换回金币。 小伟现在有
枚金币,他想要在超能力消失后拥有尽可能多的金币。
, 。
一个观察,可以发现如果我持有一个物品多天(例如在
所以我们不需要记录每天手里持有多少纪念品,统一认为今天买的纪念品,明天就立刻卖掉。
设计
然后我们求出
void Luogu_UID_748509() {
int t, n, m;
fin >> t >> n >> m;
vector<vector<int> > p(t + 1, vector<int>(n + 1));
for (int i = 1; i <= t; ++ i )
for (int j = 1; j <= n; ++ j )
fin >> p[i][j];
vector<int> dp(10010);
for (int i = 1; i < t; ++ i ) {
fill(dp.begin(), dp.end(), 0);
int tmp = m;
for (int j = 1; j <= n; ++ j )
for (int k = p[i][j]; k <= tmp; ++ k ) {
dp[k] = max(dp[k], dp[k - p[i][j]] + p[i + 1][j]);
m = max(m, dp[k] + tmp - k);
}
}
fout << m;
}
CF1234F Yet Another Substring Reverse
- 给你一个字符串
,你可以翻转一次 的任意一个子串。问翻转后 的子串中各个字符都不相同的最长子串长度。 , ,字符集大小 。
首先答案为最长的两个各个字符都不同的子串的长度和。因为两个子串一定可以通过一次旋转变得相邻。
若令
可以用
考虑优化:
也就是枚举
若我们可以预处理
那么答案为:
#include <bits/stdc++.h>
constexpr int N = 2e6 + 10, M = 20;
char str[N];
int f[N], n, a[N], g[N];
int main() {
scanf("%s", str + 1);
n = strlen(str + 1);
for (int i = 1; i <= n; ++ i ) a[i] = str[i] - 'a';
int res = 0;
for (int i = 1; i <= n; ++ i ) {
g[1 << a[i]] = f[1 << a[i]] = 1;
for (int j = 2, state = (1 << a[i]); i + j - 1 <= n; ++ j ) {
if (state >> a[i + j - 1] & 1) break;
state |= (1 << a[i + j - 1]);
g[state] = f[state] = j;
}
}
for (int i = 0; i < (1 << M); ++ i )
for (int j = 0; j < M; ++ j )
if (i >> j & 1) g[i] = std::max(g[i], g[i ^ (1 << j)]);
for (int i = 0; i < (1 << M); ++ i ) {
if (!f[i]) continue;
res = std::max(res, f[i] + g[~i & ((1 << M) - 1)]);
}
std::cout << res << '\n';
return 0;
}
CF1550E Stringforces
- 给定字符串
和整数 , 由前 小的字母或 构成。你需要将每个 替换成某个前 小的字母。定义其价值为前 个字母中最小的最大连续出现的长度,例如 的价值为 。求最大价值。 , 。
令
首先二分答案
考虑状压 DP。设
二分合法等价于
考虑转移。令
#include <bits/stdc++.h>
constexpr int N = 200009, M = 18;
int n, k;
std::string str;
int f[1 << M], nxt[N][M], sum[N][M];
int main() {
std::cin >> n >> k >> str;
str = ' ' + str;
for (int i = 1; i <= n; ++ i )
for (int j = 0; j < k; ++ j )
sum[i][j] = sum[i - 1][j] + (str[i] == j + 'a' || str[i] == '?');
auto chk = [&](int mid) -> bool {
for (int j = n + 1; j < N; ++ j )
for (int i = 0; i < k; ++ i ) nxt[j][i] = n + 1;
for (int i = n; i; -- i )
for (int j = 0; j < k; ++ j )
nxt[i][j] = i + mid - 1 <= n && sum[i + mid - 1][j] - sum[i - 1][j] == mid ? i + mid - 1 : nxt[i + 1][j];
memset(f, 0x3f, sizeof f);
f[0] = 0;
for (int i = 0; i < (1 << k); ++ i )
for (int j = 0; j < k; ++ j )
if (!(i >> j & 1)) f[i | (1 << j)] = std::min(f[i | (1 << j)], nxt[f[i] + 1][j]);
return f[(1 << k) - 1] <= n;
};
int l = 1, r = n, res = 0;
while (l <= r) {
int mid = l + r >> 1;
if (chk(mid)) res = mid, l = mid + 1;
else r = mid - 1;
}
std::cout << res;
return 0;
}
CF1316E Team Building
- 有
个人。你需要从中选出 个人作队员, 个人作观众。第 个人作观众的价值为 ,作第 个队员的价值为 。求最大价值和。 , 。
首先将人按照
接下来状压 DP。设
分类讨论第
- 若第
个人作队员,那么我们枚举 表示他要成为第 个队员。此时的价值为:
- 若第
个人不作队员。首先我们直到的是前 个人中有 个已经作为队员了,也就是说有 个人观众。如果 那么第 个人一定作观众。否则啥也不干。此时的价值为:
两种转移取较大值即可。最终答案为
#include <bits/stdc++.h>
typedef long long ll;
constexpr int N = 1e5 + 9;
constexpr ll INF = 1e12;
ll f[N][1 << 7];
struct Person {
int v;
int s[7];
bool operator <(const Person& h) const {
return v > h.v;
}
}a[N];
int main() {
int n, p, k;
std::cin >> n >> p >> k;
for (int i = 1; i <= n; ++ i ) std::cin >> a[i].v;
for (int i = 1; i <= n; ++ i )
for (int j = 0; j < p; ++ j )
std::cin >> a[i].s[j];
std::sort(a + 1, a + n + 1);
for (int i = 1; i < 1 << p; ++ i ) f[0][i] = -INF;
for (int i = 1; i <= n; ++ i )
for (int j = 0; j < 1 << p; ++ j ) {
if (__builtin_popcount(j) > i) f[i][j] = -INF;
else {
f[i][j] = f[i - 1][j] + (i - 1 - __builtin_popcount(j) < k ? a[i].v : 0);
for (int k = 0; k < p; ++ k )
if (j >> k & 1) f[i][j] = std::max(f[i][j], f[i - 1][j ^ (1 << k)] + a[i].s[k]);
}
}
std::cout << f[n][(1 << p) - 1];
return 0;
}
CF482C Game with Strings
- 小 A 有
个长度均为 的不相同的字符串,然后小 A 随机地选择其中一个,小 B 要猜这个字符串。小 B 可以问小 A:字符串中第 个字符是什么?求小 B 期望问几次能唯一确定这个字符串。 。
不妨枚举小 A 选择的字符串为
状压 DP。设
如果
复杂度过不去。思考能否省去最开始的枚举。
重新设
类似的有转移:
其中
考虑
#include <bits/stdc++.h>
typedef long long ll;
#define int ll
const int N = 51, M = 22;
int n, m;
int a[N][M];
double f[1ll << M], res;
ll g[1ll << M];
signed main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0), std::cout.tie(0);
std::cin >> n;
if (n == 1) {
std::cout << 0 << '\n';
return 0;
}
for (int i = 1; i <= n; ++ i ) {
std::string s;
std::cin >> s;
m = s.size();
for (int j = 0; j < m; ++ j )
a[i][j] = s[j] >= 'a' ? s[j] - 'a' : s[j] - 'A' + 26;
}
g[0] = (1ll << n) - 1;
for (int i = 1; i < n; ++ i )
for (int j = i + 1; j <= n; ++ j ) {
int S = 0;
for (int k = 0; k < m; ++ k )
if (a[i][k] == a[j][k]) S |= 1 << k;
g[S] |= (1ll << i - 1) | (1ll << j - 1);
}
for (int i = 0; i < m; ++ i )
for (int j = (1 << m) - 1; ~j; -- j )
if (!(j >> i & 1)) g[j] |= g[j | (1ll << i)];
for (int i = (1 << m) - 1; ~i; -- i ) {
if (!g[i]) continue;
for (int j = 0; j < m; ++ j )
if (!(i >> j & 1)) f[i] += f[i | (1ll << j)];
f[i] /= (m - __builtin_popcountll(i));
f[i] += __builtin_popcountll(g[i]);
}
std::cout << std::fixed << std::setprecision(10) << f[0] / n << '\n';
return 0;
}
CF797F Mice and Holes
个老鼠, 个洞,告诉你他们的一维坐标和 个洞的容量限制,问最小总距离。 ,坐标在 内。
不难发现每个洞内的老鼠在坐标上是连续的。因此我们将老鼠和洞按坐标排序,并将老鼠分成
设计 DP。令
单调队列维护即可。
#include <bits/stdc++.h>
typedef long long ll;
constexpr int N = 5010;
int n, m;
int x[N];
struct Mice {
int p, c;
bool operator <(const Mice& h) const {
return p < h.p;
}
}y[N];
ll sum[N];
ll f[2][N]; // 前 i 个洞,前 j 只老鼠,最小距离和
ll g[N]; // 前 i 只老鼠,到第 j 个洞的距离和
int q[N], hh, tt = -1;
signed main() {
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i ) std::cin >> x[i];
for (int i = 1; i <= m; ++ i ) std::cin >> y[i].p >> y[i].c;
std::sort(x + 1, x + n + 1);
std::sort(y + 1, y + m + 1);
for (int i = 1; i <= m; ++ i ) sum[i] = sum[i - 1] + y[i].c;
memset(f, 0x3f, sizeof f);
for (int i = 0; i <= m; ++ i ) f[i & 1][0] = 0;
auto calc = [&](int i, int j) -> ll {
return f[i - 1 & 1][j] - g[j];
};
for (int i = 1; i <= m; hh = 0, tt = -1, ++ i ) {
for (int j = 1; j <= n; ++ j ) {
g[j] = g[j - 1] + abs(x[j] - y[i].p);
}
for (int j = 0; j <= n; ++ j ) {
if (hh <= tt && j - y[i].c > q[hh]) ++ hh;
while (hh <= tt && calc(i, q[tt]) >= calc(i, j)) -- tt;
q[ ++ tt] = j;
if (j <= sum[i]) {
f[i & 1][j] = std::min(f[i - 1 & 1][j], g[j] + calc(i, q[hh]));
}
}
}
if (f[m & 1][n] > 1e12) f[m & 1][n] = -1;
std::cout << f[m & 1][n] << '\n';
return 0;
}
P1545 [USACO04DEC] Dividing the Path G
- 给定一个长为偶数
的线段。要求用若干两两不交的,长度在 之间的偶数长度线段来覆盖整条线段。给定 个区间 ,每个区间必须只被一个线段覆盖。求最少需要的线段数量。 , 。
如果一个区间
考虑剩余的允许放线段端点的位置
单调队列/线段树维护即可。
#include <bits/stdc++.h>
constexpr int N = 1000010;
int n, l, a, b;
struct Seg {
int l, r;
}t[N];
int dp[N], sum[N];
struct Tree {
int l, r, ls, rs, v;
}tr[N << 2];
void pushup(int u) {
tr[u].v = std::min(tr[tr[u].ls].v, tr[tr[u].rs].v);
}
int idx;
int build(int l, int r) {
int u = ++ idx;
tr[u].l = l, tr[u].r = r;
if (l != r) {
int mid = l + r >> 1;
tr[u].ls = build(l, mid), tr[u].rs = build(mid + 1, r);
} else tr[u].v = 1e9;
pushup(u);
return u;
}
int query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
int mid = tr[u].l + tr[u].r >> 1, res = 1e9;
if (l <= mid) res = query(tr[u].ls, l, r);
if (r > mid) res = std::min(res, query(tr[u].rs, l, r));
return res;
}
void modify(int u, int x, int d) {
if (tr[u].l == tr[u].r) tr[u].v = d;
else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(tr[u].ls, x, d);
else modify(tr[u].rs, x, d);
pushup(u);
}
}
int main() {
std::cin >> n >> l >> a >> b;
for (int i = 1; i <= n; ++ i ) {
std::cin >> t[i].l >> t[i].r;
++ sum[t[i].l + 1], -- sum[t[i].r];
}
for (int i = 1; i <= l; ++ i ) sum[i] += sum[i - 1];
build(1, l + 1);
memset(dp, 0x3f, sizeof dp);
dp[0] = 0;
modify(1, 1, 0);
for (int i = 2; i <= l; i += 2 )
if (!sum[i]) {
int L = std::max(0, i - 2 * b), R = i - 2 * a;
for (int j = L; j <= R; ++ j ) dp[i] = std::min(dp[i], dp[j] + 1);
modify(1, i + 1, dp[i]);
}
if (dp[l] >= 1e9) dp[l] = -1;
std::cout << dp[l];
return 0;
}
CF900D Unusual Sequences
- 给定
。求有多少个序列的 为 ,和为 。取模 。 。
设答案为
显然答案
考虑:
表示和为 的序列个数,即全集。显然插板法 ; 表示和为 的 为 的序列个数。显然 。
那么转移为:
#include <bits/stdc++.h>
typedef long long ll;
constexpr int P = 1e9 + 7;
int x, y;
std::map<int, int> f;
int fpm(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (ll)res * a % P;
b >>= 1, a = (ll)a * a % P;
}
return res;
}
int dp(int x) {
if (f.count(x)) return f[x];
if (x == 1) return f[x] = 1;
int res = fpm(2, x - 1);
for (int i = 1; i <= x / i; ++ i )
if (x % i == 0) {
res = (res - dp(i) + P) % P;
if (i != x / i && x / i != x) res = (res - dp(x / i) + P) % P;
}
return f[x] = res;
}
int main() {
std::cin >> x >> y;
std::cout << (y % x == 0 ? dp(y / x) : 0) << '\n';
return 0;
}
CF79D Password
你有
个灯泡,一开始都未点亮。 同时你有
个长度,分别为 。 每次你可以选择一段连续的子序列,且长度为某个
,并将这些灯泡的明灭状态取反。 求最少的操作次数,使得最后有且仅有
个位置是亮的,这些位置已经给定,为 。
, , 。
设
第一个观察是,我们从
所以最开始我们让所有
给定
。每次可以选择一个 的区间 取反。求将 全部变为 的最少操作数。
区间反转用差分维护。令
显然当
给定
。每次可以选择一个 的区间 ,并将 取反。求将 全部变为 的最少操作数。
显然我们只需要考虑那些为
若令
考虑状压 DP。令
转移显然:
答案为
考虑
具体的,考虑建图。对于一条边
#include <bits/stdc++.h>
constexpr int N = 10009, K = 22, L = 209;
int n, k, l, x[K], a[L];
bool b[N], c[N];
int mp[L][L];
int Id[N], Di[N], cnt;
int f[1 << K];
struct Gragh {
int h[N], e[N * L], ne[N * L], idx = 1;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, ne[idx] = h[b], h[b] = idx ++ ;
}
int dis[N];
bool st[N];
void bfs(int s) {
std::queue<int> q;
q.push(s);
memset(dis, 0x3f, sizeof dis);
memset(st, 0, sizeof st);
dis[s] = 0;
st[s] = true;
while (q.size()) {
int u = q.front();
q.pop();
for (int i = h[u]; i; i = ne[i]) {
int v = e[i];
if (!st[v]) {
st[v] = true;
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
for (int i = 1; i <= n + 1; ++ i )
if (c[i]) mp[Di[s]][Di[i]] = dis[i];
}
}G;
int dp(int S) {
if (!S) return 0;
if (f[S]) return f[S];
int &res = f[S];
res = 1e9;
for (int i = 0; i < cnt; ++ i )
if (S >> i & 1)
for (int j = 0; j < cnt; ++ j )
if (S >> j & 1)
res = std::min(res, dp(S ^ (1 << i) ^ (1 << j)) + mp[i][j]);
return res;
}
int main() {
std::cin >> n >> k >> l;
for (int i = 1; i <= k; ++ i ) {
std::cin >> x[i];
b[x[i]] = true;
}
for (int i = 1; i <= n + 1; ++ i ) {
c[i] = b[i] ^ b[i - 1];
}
for (int i = 1; i <= l; ++ i ) {
std::cin >> a[i];
}
for (int i = 1; i <= n + 1; ++ i )
if (c[i]) Id[cnt ++ ] = i, Di[i] = cnt - 1;
for (int i = 1; i <= n + 1; ++ i )
for (int j = 1; j <= l; ++ j )
if (i + a[j] <= n + 1) G.add(i, i + a[j]);
for (int i = 1; i <= n + 1; ++ i )
if (c[i]) G.bfs(i);
std::cout << (dp((1 << cnt) - 1) == 1e9 ? -1 : f[(1 << cnt) - 1]) << '\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现