Github 链接:Daily Practice
题目链接:洛谷 P1043
题意:将 n n n ( n ≤ 50 n \le 50 n≤50) 个环形数字分成 m m m ( m ≤ 9 m \le 9 m≤9) 组,求每一组和在模 10 10 10 后乘积的最小值和最大值。
错误解法和代码
利用 dfs
找出每一个分组的边界,再对数字维护一个前缀和,暴力求出每一种分组方案的结果。由于这种做法的时间复杂度是 O ( n m ) O(n^m) O(nm) 最后导致了 TLE
。
#include
using namespace std;
int n, m, a[55], pre[55], maxm = 0, minm = INT_MAX;
void dfs(int cur, vector<int>& v) {
if (v.size() >= m) {
int res = (pre[v[0]] + pre[n] - pre[v.back()] + 1000000) % 10;
for (int i = 1; i < v.size(); i++)
res *= ((pre[v[i]] - pre[v[i - 1]] + 1000000) % 10);
maxm = max(maxm, res), minm = min(minm, res);
return;
}
if (cur >= n) return;
for (int i = cur + 1; i <= n; i++) {
v.push_back(i);
dfs(i, v);
v.pop_back();
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i];
vector<int> v;
dfs(0, v);
cout << minm << endl << maxm << endl;
return 0;
}
正确解法和代码
区间 D P DP DP:将环形数字倍长之后可以将其当作线形数字来处理。设 maxm l , r , k \text{maxm}_{l, r, k} maxml,r,k, minm l , r , k \text{minm}_{l, r, k} minml,r,k 分别为 [ l , r ] [l, r] [l,r] 区间内,将线性数字分为 k k k 段的最大、最小值,具体状态转移方程看代码。
时间复杂度: O ( n 3 m ) O(n^3m) O(n3m)。
#include
using namespace std;
#define int long long
int n, m, a[105], maxm[105][105][10], minm[105][105][10];
signed main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i], a[i + n] = a[i];
for (int i = 1; i <= 2 * n; i++) a[i] += a[i - 1];
for (int i = 1; i <= 100; i++)
for (int j = 1; j <= 100; j++)
for (int k = 0; k < 10; k++) maxm[i][j][k] = 0, minm[i][j][k] = 1e9;
// cout << minm[0][0][0];
for (int i = 1; i <= 2 * n; i++)
for (int j = 1; j <= 2 * n; j++) {
maxm[i][j][1] = minm[i][j][1] = ((a[j] - a[i - 1]) % 10 + 10) % 10;
}
for (int len = 2; len <= n; len++) {
for (int l = 1; l <= n; l++) {
int r = l + len - 1;
for (int k = 2; k <= m; k++) {
if (len < k) continue;
for (int p = l; p < r; p++) {
maxm[l][r][k] = max(maxm[l][r][k], maxm[l][p][k - 1] * maxm[p + 1][r][1]);
minm[l][r][k] = min(minm[l][r][k], minm[l][p][k - 1] * minm[p + 1][r][1]);
}
}
}
}
int ans1 = LLONG_MAX, ans2 = 0;
for (int i = 1; i <= n; i++)
ans1 = min(ans1, minm[i][i + n - 1][m]), ans2 = max(ans2, maxm[i][i + n - 1][m]);
// for (int i = 1; i <= n; i++) cout << minm[i][i + n - 1][m] << " ";
cout << ans1 << endl << ans2 << endl;
return 0;
}
题目链接:洛谷 P1121
题意:给出一段长度为 n n n 的环形序列,选出两个不重合的非空段,使得两段的和最大。
错误解法及代码
将环形序列倍长当成线性序列处理。设 d p i , 0 / 1 / 2 dp_{i, 0/1/2} dpi,0/1/2 表示以 i i i 结尾选取了 0 / 1 / 2 0/1/2 0/1/2 个子段的最大和。状态转移方程为 d p i , 1 = max ( d p i − 1 , 1 + a i , a i ) dp_{i, 1} = \max(dp_{i - 1, 1} + a_i, a_i) dpi,1=max(dpi−1,1+ai,ai), d p i , 2 = max ( d p i − 1 , 2 + a i , max j = l i − 1 d p j , 1 + a i ) dp_{i, 2} = \max(dp_{i - 1, 2} + a_i, \max_{j = l}^{i - 1}dp_{j, 1} + a_i) dpi,2=max(dpi−1,2+ai,maxj=li−1dpj,1+ai)。时间复杂度为 O ( n 2 ) O(n^2) O(n2) 导致了 TLE
,并且可能做法或者代码导致有误导致了 WA
。
#include
using namespace std;
const int N = 4e5 + 5;
int n, a[N], dp[N][3], maxm[N], ans = 0;
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], a[i + n] = a[i];
for (int l = 1; l <= n; l++) {
maxm[l] = dp[l][1] = a[l], dp[l][0] = dp[l][2] = 0;
for (int i = l + 1; i <= l + n - 1; i++) {
dp[i][1] = max(dp[i - 1][1] + a[i], a[i]);
dp[i][2] = max({dp[i - 1][2] + a[i], maxm[i - 1] + a[i], dp[i - 1][1] + a[i]});
maxm[i] = max(maxm[i - 1], dp[i][1]);
ans = max(ans, dp[i][2]);
}
}
cout << ans << endl;
return 0;
}
正确解法及代码
对于一个长度为 n n n 的环形序列,选取两个最大子段使其和最大,只有可能有以下两种选法(用 1 1 1 表示选择对应元素, 0 0 0 表示不选对应元素):
对于第 1 1 1 种情况,直接利用 d p dp dp 分别处理出前缀、后缀的最大子段和,然后枚举分界点找出两段和的最大值即可。对于第二种情况,我们可以不找 1 1 1 的部分,去找 0 0 0 的部分,也就是将原序列全部变为相反数后,再找两段最大的子段和,然后再将该结果加上原序列的总和即为答案。
特判:整个序列只有一个正数时,答案就是序列中两个最大的值的和。
时间复杂度: O ( n ) O(n) O(n)。
#include
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n, a[N], pre[N], suf[N], sum = 0, num = 0;
int query() {
for (int i = 1; i <= n; i++) pre[i] = max(pre[i - 1], 0LL) + a[i];
for (int i = 2; i <= n; i++) pre[i] = max(pre[i], pre[i - 1]);
for (int i = n; i; i--) suf[i] = max(suf[i + 1], 0LL) + a[i];
for (int i = n - 1; i; i--) suf[i] = max(suf[i], suf[i + 1]);
int res = LLONG_MIN;
for (int i = 1; i < n; i++) res = max(res, pre[i] + suf[i + 1]);
return res;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i], sum += a[i], num += (a[i] > 0);
int res1 = query();
if (num == 1) cout << res1 << endl;
else {
for (int i = 1; i <= n; i++) a[i] = -a[i];
int res2 = sum + query();
if (!res2) res2 = LLONG_MIN;
cout << max(res1, res2) << endl;
}
return 0;
}
题目链接:Codeforces Round 1031 A
题意:给定一个初始温度 k k k,当温度大于等于 a a a 时,可以进行第一种操作使温度下降 x x x 度;当温度大于等于 b b b 时,可以进行第二种操作使温度下降 y y y 度。求最多操作次数。
正确思路及代码
贪心。
不妨假设 a > b a > b a>b ( a ≤ b a \le b a≤b 时分别交换 ( a , b ) (a, b) (a,b) 和 ( x , y ) (x, y) (x,y) 即可)
时间复杂度: O ( 1 ) O(1) O(1)。
#include
using namespace std;
#define int long long
void solve() {
int k, a, b, x, y;
cin >> k >> a >> b >> x >> y;
if (a < b) swap(a, b), swap(x, y);
int res = 0;
if (x < y) {
int res1 = max(0LL, (k - a + x) / x), res2 = max(0LL, (k - res1 * x - b + y) / y);
res = max(res1 + res2, res);
} else {
res = max(res, (k - b + y) / y);
}
cout << res << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int T;
cin >> T;
while (T--) solve();
return 0;
}
题目链接:Codeforces Round 1031 B
题意:平面内给定一个从 ( 0 , 0 ) (0, 0) (0,0) 到 ( w , h ) (w, h) (w,h) 的矩形,需要用大小为 a × b a \times b a×b 纸张去覆盖整个矩形,纸张可以超出矩形边界,但是不能旋转、不能重叠。现在矩形内已经放有两张纸,问是否能在不移动这两张纸的前提下,按照要求用纸张覆盖整个矩形。
正确思路及代码
当 ( x 1 − x 2 ) % a = 0 (x_1 - x_2) \% a = 0 (x1−x2)%a=0 或 ( y 1 − y 2 ) % b = 0 (y_1 - y_2) \% b = 0 (y1−y2)%b=0 时输出 Yes
。
特判:当 x 1 = x 2 x_1 = x_2 x1=x2 或 y 1 = y 2 y_1 = y_2 y1=y2 时需要判断另一个不相等的坐标的差是否能整除 a a a 或者 b b b。
时间复杂度: O ( 1 ) O(1) O(1)。
#include
using namespace std;
#define int long long
int w, h, a, b, x1, Y1, x2, y2;
void solve() {
cin >> w >> h >> a >> b >> x1 >> Y1 >> x2 >> y2;
if (x1 == x2) {
if (abs(Y1 - y2) % b == 0) cout << "Yes\n";
else cout << "No\n";
} else if (Y1 == y2) {
if (abs(x1 - x2) % a == 0) cout << "Yes\n";
else cout << "No\n";
} else {
if ((x1 - x2) % a == 0 || (Y1 - y2) % b == 0) cout << "Yes\n";
else cout << "No\n";
}
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int T;
cin >> T;
while (T--) solve();
return 0;
}
题目链接:Codeforces Round 1031 C
题意:给出大小为 n × m n \times m n×m ( n , m ≤ 500 n, m \le 500 n,m≤500)的矿区,每一个格子可以是金矿 ( g g g)、石头 ( # \# #) 或者是空地 ( . . .)。收集金矿的方法为在一个空地安放炸药,以炸药为中心边长为 2 k + 1 2k + 1 2k+1 ( k ≤ 500 k \le 500 k≤500) 的正方形内,最边缘的金矿可以被收集,其余位置全部变为空地。问最多能收集多少金矿。
正确思路及代码
可以发现无论 k k k 等于多少,第二次操作开始一定不会损失金矿,所以第一次操作只需要选择损失金矿最少的空地进行操作即可。为了快速查询区间内的金矿数量,可以利用二维前缀和对金矿数量进行维护。
时间复杂度: O ( n m ) O(nm) O(nm)。
#include
using namespace std;
#define int long long
int n, m, k, pre[505][505];
void solve() {
cin >> n >> m >> k;
string s[505];
for (int i = 1; i <= n; i++) cin >> s[i], s[i] = " " + s[i];
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++) pre[i][j] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + (s[i][j] == 'g');
int res = INT_MAX;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (s[i][j] == '.') {
int maxx = min(n, i + k - 1), maxy = min(m, j + k - 1);
int minx = max(1LL, i - k + 1), miny = max(1LL, j - k + 1);
int tmp = pre[maxx][maxy] - pre[minx - 1][maxy] - pre[maxx][miny - 1] + pre[minx - 1][miny - 1];
res = min(res, tmp);
}
cout << pre[n][m] - res << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int T;
cin >> T;
while (T--) solve();
return 0;
}
题目链接:洛谷 P1226
题意:机器人是一个直径为 1.6 1.6 1.6 米的求,在一个 N × M N \times M N×M 的网格上,有的格子为不可移动的障碍。机器人可以接受向前 1 1 1 步、向前 2 2 2 步、向前 3 3 3 步、左转、右转五个指令,执行每个指令需要 1 1 1 秒,问从起点走到终点最少要多少秒。
正确思路及代码
将到达所有点的时间初始化为无穷大,利用 b f s bfs bfs 找出依次处理出到每一个点的最小时间(每当找到到达一个点的时间比之前的小,依次更新往后的所有节点,类似于图论中的松弛操作)。
时间复杂度: O ( n 2 m 2 ) O(n^2m^2) O(n2m2)。
#include
using namespace std;
const int dx[4] = { 0, 1, 0, -1 }, dy[4] = { 1, 0, -1, 0 };
unordered_map<char, int> mp = { {'E', 0}, {'S', 1}, {'W', 2}, {'N', 3}};
int n, m, sx, sy, tx, ty, a[55][55], ans[55][55], vis[55][55], dir;
void bfs() {
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++) ans[i][j] = INT_MAX, vis[i][j] = 0;
queue<tuple<int, int, int>> q;
q.push({dir, sx, sy});
ans[sx][sy] = 0;
while (!q.empty()) {
int d = get<0>(q.front()), x = get<1>(q.front()), y = get<2>(q.front());
q.pop();
for (int i = 0; i < 4; i++) {
for (int j = 1; j <= 3; j++) {
int nx = x + dx[i] * j, ny = y + dy[i] * j;
if (nx <= 0 || nx >= n || ny <= 0 || ny >= m || a[nx][ny]) break;
int time = min((d - i + 4) % 4, (i - d + 4) % 4) + 1;
if (ans[nx][ny] > ans[x][y] + time) {
q.push({i, nx, ny});
ans[nx][ny] = ans[x][y] + time;
}
}
}
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j]) a[i - 1][j] = a[i][j - 1] = a[i - 1][j - 1] = a[i][j];
char c;
cin >> sx >> sy >> tx >> ty >> c;
dir = mp[c];
bfs();
if (ans[tx][ty] < INT_MAX) cout << ans[tx][ty] << endl;
else cout << -1 << endl;
return 0;
}
题目链接:洛谷 P1198
正确思路及代码
线段树模板题,单点修改 + 区间查询。
时间复杂度: O ( M log l e n ) O(M\log len) O(Mloglen)。
#include
using namespace std;
#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
const int N = 4e5 + 5;
int m, d, t[N << 2], len = 0, ans = 0;
void insert(int q, int k, int p = 1, int l = 1, int r = N - 1) {
if (l == r) {
t[p] = k % d;
return;
}
int mid = (l + r) >> 1;
if (q <= mid) insert(q, k, ls, l, mid);
else insert(q, k, rs, mid + 1, r);
t[p] = max(t[ls], t[rs]);
}
int query(int ql, int qr, int p = 1, int l = 1, int r = N - 1) {
if (ql <= l && r <= qr) return t[p];
int mid = (l + r) >> 1, oup = 0;
if (ql <= mid) oup = max(oup, query(ql, qr, ls, l, mid));
if (qr > mid) oup = max(oup, query(ql, qr, rs, mid + 1, r));
return oup;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> m >> d;
while (m--) {
char op; int num;
cin >> op >> num;
if (op == 'Q') {
ans = query(len - num + 1, len);
cout << ans << "\n";
}
if (op == 'A') {
insert(++len, ans + num);
}
}
return 0;
}
题目链接:洛谷 P1314
错误思路及代码
错误原因:二分边界处理错误。
#include
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n, m, s, w[N], v[N], prea[N], preb[N], L[N], R[N];
int cal(int W) {
memset(prea, 0, sizeof(prea)), memset(preb, 0, sizeof(preb));
for (int i = 1; i <= n; i++) {
prea[i] = prea[i - 1], preb[i] = preb[i - 1];
if (w[i] >= W) prea[i]++, preb[i] += v[i];
}
int sum = 0;
for (int i = 1; i <= m; i++)
sum += (prea[R[i]] - prea[L[i] - 1]) * (preb[R[i]] - preb[L[i] - 1]);
return sum;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m >> s;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= m; i++) cin >> L[i] >> R[i];
int l = 0, r = 2e6 + 10;
while (l < r) {
int mid = (l + r) >> 1;
if (cal(mid) <= s) r = mid;
else l = mid + 1;
}
cout << min(abs(s - cal(r)), abs(s - cal(r + 1))) << endl;
return 0;
}
正确思路及代码
这道题目可以发现以下性质
时间复杂度: O ( ( n + m ) log W ) O((n + m)\log W) O((n+m)logW)。
#include
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n, m, s, w[N], v[N], prea[N], preb[N], L[N], R[N];
int cal(int W) {
memset(prea, 0, sizeof(prea)), memset(preb, 0, sizeof(preb));
for (int i = 1; i <= n; i++) {
prea[i] = prea[i - 1], preb[i] = preb[i - 1];
if (w[i] >= W) prea[i]++, preb[i] += v[i];
}
int sum = 0;
for (int i = 1; i <= m; i++)
sum += (prea[R[i]] - prea[L[i] - 1]) * (preb[R[i]] - preb[L[i] - 1]);
return sum;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m >> s;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= m; i++) cin >> L[i] >> R[i];
int l = 0, r = 1e6 + 1;
while (l + 1 < r) {
int mid = (l + r) >> 1;
if (cal(mid) <= s) r = mid;
else l = mid;
}
cout << min(abs(s - cal(r)), abs(s - cal(l))) << endl;
return 0;
}
题目链接:洛谷 P1343
题意:给出一个 n n n 个点 m m m 条边的带权有向图,边权为最大学生容纳量,学生要从 1 1 1 号节点逃跑到 n n n 号节点,问每一批最多能运送多少学生,最少需要多少批才能运送完全部学生。
错误思路及代码
错误原因:没有开 long long
。
#include
using namespace std;
const int N = 205, M = 2005;
int n, m, x, head[N], num = 1, d[N];
struct edge { int to, nxt, w; } e[M << 1];
void addEdge(int u, int v, int w) {
e[++num] = (edge){ v, head[u], w }, head[u] = num;
}
bool bfs() {
memset(d, 0, sizeof(d));
queue<int> q;
q.push(1);
d[1] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to, w = e[i].w;
if (!d[v] && w) {
d[v] = d[u] + 1;
q.push(v);
}
}
}
return d[n];
}
int dinic(int cur, int flow) {
if (cur == n) return flow;
int res = flow;
for (int i = head[cur]; i; i = e[i].nxt) {
if (!res) return flow;
int v = e[i].to, w = e[i].w;
if (!w) continue;
if (d[v] != d[cur] + 1) continue;
int k = dinic(v, min(res, w));
if (!k) d[v] = 0;
res -= k, e[i].w -= k, e[i ^ 1].w += k;
}
return flow - res;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m >> x;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
addEdge(a, b, c), addEdge(b, a, 0);
}
int maxFlow = 0, flow = 0;
while (bfs()) {
while (flow = dinic(1, INT_MAX)) maxFlow += flow;
}
if (!maxFlow) cout << "Orz Ni Jinan Saint Cow!" << endl;
else cout << maxFlow << " " << (x + maxFlow - 1) / maxFlow << endl;
return 0;
}
正确思路及代码
最大流模板题,利用 dinic
算法算出从起点 1 1 1 到终点 n n n 的最大流,这个数值 maxFlow
就是每批能运出的最多学生数量,所以运送学生最少需要 ⌈ x m a x F l o w ⌉ = ⌊ m a x F l o w + x − 1 m a x F l o w ⌋ \lceil \frac{x}{maxFlow} \rceil = \lfloor \frac{maxFlow + x - 1}{maxFlow} \rfloor ⌈maxFlowx⌉=⌊maxFlowmaxFlow+x−1⌋ 批。当 m a x F l o w = 0 maxFlow = 0 maxFlow=0 时无法到达目的地,输出 Orz Ni Jinan Saint Cow!
。
时间复杂度: O ( n 2 m ) O(n^2m) O(n2m)。
#include
using namespace std;
#define int long long
const int N = 205, M = 2005;
int n, m, x, head[N], num = 1, d[N];
struct edge { int to, nxt, w; } e[M << 1];
void addEdge(int u, int v, int w) {
e[++num] = (edge){ v, head[u], w }, head[u] = num;
}
bool bfs() {
memset(d, 0, sizeof(d));
queue<int> q;
q.push(1);
d[1] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to, w = e[i].w;
if (!d[v] && w) {
d[v] = d[u] + 1;
q.push(v);
}
}
}
return d[n];
}
int dinic(int cur, int flow) {
if (cur == n) return flow;
int res = flow;
for (int i = head[cur]; i; i = e[i].nxt) {
if (!res) return flow;
int v = e[i].to, w = e[i].w;
if (!w) continue;
if (d[v] != d[cur] + 1) continue;
int k = dinic(v, min(res, w));
if (!k) d[v] = 0;
res -= k, e[i].w -= k, e[i ^ 1].w += k;
}
return flow - res;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m >> x;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
addEdge(a, b, c), addEdge(b, a, 0);
}
int maxFlow = 0, flow = 0;
while (bfs()) {
while (flow = dinic(1, LLONG_MAX)) maxFlow += flow;
}
if (!maxFlow) cout << "Orz Ni Jinan Saint Cow!" << endl;
else cout << maxFlow << " " << (x + maxFlow - 1) / maxFlow << endl;
return 0;
}
题目链接:洛谷 P1792
题意:给出 n n n 个环形的种树位置,编号按顺时针从 1 1 1 到 n n n,每一个位置种树后会得到 A i A_i Ai 的美观度,总共要种 m m m 棵树,求最大美观度,如果没有合法的种树方案,输出 Error!
。
正确思路及代码
反悔贪心 + 双向链表。
不合法情况: m > n 2 m > \frac{n}{2} m>2n,因为每两棵树之间之隔一个位置是能种最多树的方案,总共能种 n 2 \frac{n}{2} 2n 棵树。
要求最大美观度肯定是要尽量多的选择剩余种树位置中美观度最大的位置去种树,但是由于存在相邻位置每次都选择当前最大美观度的位置种树不一定最优,所以要加入一个反悔操作。
例如,对于下面这组样例
4 2
1 6 7 6
答案是 12 12 12,但是如果每一次都是选择剩余可以种树位置的最大美观度的位置去种树的话,算出来的答案就会是 1 + 7 = 8 1 + 7 = 8 1+7=8。
如何加入反悔操作?
可以发现,假如按照每一次选一个种树位置的方式去选 m m m 个种树位置,如果要进行反悔,肯定是要将已经选择的某个位置 i i i 不选,改为同时选择这个位置旁边两个位置 l i l_i li 和 r i r_i ri, l i l_i li 和 r i r_i ri 是当前第 i i i 个位置左右相邻的种树位置。对于上面的样例,如果第一轮选择了 7 7 7,第二轮的反悔实际上就是不选 7 7 7 改为选旁边的两个 6 6 6。
这样的反悔操作,实际上是让答案增大了 a l i + a r i − a i a_{l_i} + a_{r_i} - a_i ali+ari−ai。所以每一次操作之后我们只需要将 i i i, l i l_i li, r i r_i ri 这三个种树位置种删除,再在这个位置插入一个美观度为 a l i + a r i − a i a_{l_i} + a_{r_i} - a_i ali+ari−ai 的种树位置即可。
时间复杂度: O ( n log n ) O(n\log n) O(nlogn)。
#include
using namespace std;
const int N = 4e5 + 5;
int n, m, a[N], l[N], r[N], vis[N];
priority_queue<pair<int, int>> q;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
l[i] = i - 1, r[i] = i + 1;
q.push({a[i], i});
}
r[n] = 1, l[1] = n;
if (m > n / 2) {
cout << "Error!" << endl;
return 0;
}
memset(vis, 0, sizeof(vis));
int ans = 0, idx = n;
for (int i = 1; i <= m && !q.empty(); i++) {
while (!q.empty() && vis[q.top().second]) q.pop();
int val = q.top().first, pos = q.top().second;
ans += val;
vis[pos] = vis[l[pos]] = vis[r[pos]] = 1;
a[++idx] = a[l[pos]] + a[r[pos]] - a[pos];
l[idx] = l[l[pos]], r[idx] = r[r[pos]];
r[l[l[pos]]] = idx, l[r[r[pos]]] = idx;
q.push({a[idx], idx});
}
cout << ans << endl;
return 0;
}
题目链接:洛谷 P1903
题意:给出 n n n 支彩笔,每一支彩笔用一个整数代表其颜色。进行 m m m 次操作,Q L R
表示询问区间 [ L , R ] [L, R] [L,R] 内总共有多少支彩笔,R P C
表示将第 P P P 支彩笔的颜色改为 C C C。
正确思路及代码
离线操作,带修改的莫队模板题。将所有操作读入后,给所有询问操作记录一个修改时间,表示是在多少次修改后进行的询问。按照每次询问左端点、右端点所在的块以及时间分别为第一、二、三关键字进行排序,接着用莫队找出答案。
时间复杂度: O ( n 2 3 m 2 3 t 1 3 ) O(n^{\frac{2}{3}}m^{\frac{2}{3}}t^{\frac{1}{3}}) O(n32m32t31)。
#include
using namespace std;
const int N = 133335, M = 1e6 + 5;
int n, m, a[N], vis[M], tot = 0, cntq = 0, cntu = 0, ans[N], p[N];
struct query { int l, r, t; } q[N];
struct update { int x, y; } u[N];
void add(int x) {
if (!vis[x]) tot++;
vis[x]++;
}
void del(int x) {
vis[x]--;
if (!vis[x]) tot--;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
int len = pow(n, 2.0 / 3.0);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= m; i++) {
char c; int x, y;
cin >> c >> x >> y;
if (c == 'Q') q[++cntq] = (query){ x, y, cntu };
if (c == 'R') u[++cntu] = (update){ x, y };
p[i] = i;
}
sort(p + 1, p + cntq + 1, [&](int x, int y){
if (q[x].l / len == q[y].l / len) {
if (q[x].r / len == q[y].r / len) return q[x].t < q[y].t;
return q[x].r / len < q[y].r / len;
}
return q[x].l / len < q[y].l / len;
});
int L = 1, R = 0, T = 0;
for (int i = 1; i <= cntq; i++) {
while (L < q[p[i]].l) del(a[L++]);
while (R > q[p[i]].r) del(a[R--]);
while (L > q[p[i]].l) add(a[--L]);
while (R < q[p[i]].r) add(a[++R]);
while (T < q[p[i]].t) {
T++;
if (u[T].x >= L && u[T].x <= R) {
add(u[T].y);
del(a[u[T].x]);
}
swap(a[u[T].x], u[T].y);
}
while (T > q[p[i]].t) {
if (u[T].x >= L && u[T].x <= R) {
add(u[T].y);
del(a[u[T].x]);
}
swap(a[u[T].x], u[T].y);
T--;
}
ans[p[i]] = tot;
}
for (int i = 1; i <= cntq; i++) cout << ans[i] << endl;
return 0;
}
题目链接:洛谷 P1575
题意:给出一个只含 true
,false
,or
,and
,not
和空格的字符串表达式,判断这个字符串表达式最后的结果是 true
还是 false
,如果表达式不合法则输出 error
。
正确思路及代码
按照题意模拟。
#include
using namespace std;
unordered_map<string, int> mp = {{"true", 1}, {"false", 0}};
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
string s, str;
stack<string> op;
stack<int> num;
int flag = 0, tag = 0;
getline(cin, s);
s += " ";
for (int i = 0; i < (int)s.size(); i++) {
if (s[i] == ' ') {
if (str == "and" || str == "or") {
if (flag || num.size() != op.size() + 1) {
cout << "error" << endl;
return 0;
}
op.push(str);
tag |= 1;
} else if (str == "not") {
flag ^= 1;
tag |= 2;
} else {
if (flag) {
if (str == "true") str = "false";
else str = "true";
flag = 0;
}
if (num.size() != op.size()) {
cout << "error" << endl;
return 0;
}
int val = mp[str];
while (!num.empty() && !op.empty() && op.top() == "and") {
val &= num.top();
op.pop(), num.pop();
}
num.push(val);
tag |= 4;
}
str = "";
} else str += s[i];
}
if (flag || ((tag & 4) == 0)) {
cout << "error" << endl;
return 0;
}
int ans = 0;
while (!num.empty()) ans |= num.top(), num.pop();
if (ans) cout << "true" << endl;
else cout << "false" << endl;
return 0;
}
题目链接:洛谷 P2389
题意:给出 n n n ( n ≤ 500 n \le 500 n≤500) 个 a i a_i ai ( − 1000 ≤ a i ≤ 1000 -1000 \le a_i \le 1000 −1000≤ai≤1000),选出不超过 k k k ( k ≤ n k \le n k≤n) 段 a i a_i ai,使得其总和最大。
正确思路及代码
设 d p i , j , 0 / 1 dp_{i,j,0/1} dpi,j,0/1 表示考虑到第 i i i 位,已经选择了 j j j 段连续 a i a_i ai,选/不选第 i i i 位的总和的最大值。
最后答案为 max j = 0 k d p n , j , 0 / 1 \max_{j = 0}^k dp_{n, j, 0/1} maxj=0kdpn,j,0/1。
#include
using namespace std;
const int N = 505;
int n, k, dp[N][N][2], a[N];
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= k; j++) {
if (!j) dp[i][j][0] = dp[i - 1][j][0], dp[i][j][1] = INT_MIN;
else {
dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1]);
dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0]) + a[i];
}
}
}
int ans = 0;
for (int j = 0; j <= k; j++) ans = max({ans, dp[n][j][0], dp[n][j][1]});
cout << ans << endl;
return 0;
}
题目链接:洛谷 P1360
题意:给出 m m m 个能力, n n n 个数字,其二进制表示下的 1 1 1 表示对应能力得到提升。定义均衡时期为一段时间内每个能力都提升了相同的次数,问最大的均衡时期。
正确思路及代码
对于 1 ≤ i ≤ m 1 \le i \le m 1≤i≤m,我们要找到一对 [ l , r ] [l, r] [l,r] 满足 p r e r , i − p r e l − 1 , i = k pre_{r, i} - pre_{l - 1, i} = k prer,i−prel−1,i=k ( k k k 为定值),即对于 1 < i ≤ m 1 < i \le m 1<i≤m,满足 p r e r , i − p r e r , i − 1 = p r e l − 1 , i − p r e l − 1 , i pre_{r, i} - pre_{r, i - 1} = pre_{l - 1, i} - pre_{l - 1, i} prer,i−prer,i−1=prel−1,i−prel−1,i。所以对于每一个位置 j j j,我们只需要找出前面某一个位置 k k k 满足 p r e j , i − p r e j , i − 1 = p r e k , i − p r e k , i − 1 pre_{j, i} - pre_{j, i - 1} = pre_{k, i} - pre_{k, i - 1} prej,i−prej,i−1=prek,i−prek,i−1 即可。
时间复杂度: O ( n m log n ) O(nm\log n) O(nmlogn)。
#include
using namespace std;
const int N = 1e5 + 5;
int n, m, ans = 0;
map<vector<int>, int> mp;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
vector<int> pre(m, 0);
mp[pre] = 0;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
for (int j = m - 1; j >= 0; j--)
if (x & (1LL << j)) pre[j]++;
vector<int> now(m);
for (int j = 1; j < m; j++) now[j] = pre[j] - pre[j - 1];
if (mp.count(now)) ans = max(ans, i - mp[now]);
else mp[now] = i;
}
cout << ans << "\n";
return 0;
}
题目链接:洛谷 P1472
题意:一个有 n n n 个节点,深度为 k k k 的无标号完满二叉树(即每个节点的儿子数为 0 0 0 或 2 2 2)有多少种结构?定义根节点深度为 1 1 1,答案对 9901 9901 9901 取模。
正确思路及代码
时间复杂度: O ( n 2 k ) O(n^2k) O(n2k)
#include
using namespace std;
const int mod = 9901;
int n, k, dp[205][105];
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> k;
for (int j = 1; j <= k; j++) dp[1][j] = 1;
for (int j = 1; j <= k; j++)
for (int i = 3; i <= n; i += 2)
for (int t = 1; t <= i - 1; t += 2)
dp[i][j] = (dp[i][j] + dp[t][j - 1] * dp[i - t - 1][j - 1] % mod) % mod;
cout << (dp[n][k] - dp[n][k - 1] + mod) % mod << endl;
return 0;
}
题目链接:洛谷 P1545
正确思路及代码
DP + 线段树优化。
时间复杂度: O ( N + L log L ) O(N + L\log L) O(N+LlogL)。
#include
using namespace std;
#define ls (p << 1)
#define rs (p << 1 | 1)
#define int long long
const int L = 1e6 + 5;
int n, len, a, b, dp[L], vis[L], t[L << 3];
void build(int p = 1, int l = 0, int r = len) {
if (l == r) {
t[p] = INT_MAX;
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
t[p] = min(t[ls], t[rs]);
}
void modify(int q, int k, int p = 1, int l = 0, int r = len) {
if (l == r) {
t[p] = k;
return;
}
int mid = (l + r) >> 1;
if (q <= mid) modify(q, k, ls, l, mid);
else modify(q, k, rs, mid + 1, r);
t[p] = min(t[ls], t[rs]);
}
int query(int ql, int qr, int p = 1, int l = 0, int r = len) {
if (ql <= l && r <= qr) return t[p];
int mid = (l + r) >> 1, ans = INT_MAX;
if (ql <= mid) ans = min(ans, query(ql, qr, ls, l, mid));
if (qr > mid) ans = min(ans, query(ql, qr, rs, mid + 1, r));
return ans;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> len >> a >> b;
for (int i = 0; i <= len; i++) dp[i] = 0;
for (int i = 1; i <= n; i++) {
int s, e;
cin >> s >> e;
vis[s + 1]++, vis[e]--;
}
for (int i = 1; i <= len; i++) vis[i] += vis[i - 1];
build();
modify(0, 0);
for (int i = 2 * a; i <= len; i += 2) {
if (vis[i]) continue;
int ql = max(0LL, i - 2 * b), qr = i - 2 * a;
dp[i] = query(ql, qr) + 1;
modify(i, dp[i]);
}
if (dp[len] >= INT_MAX) dp[len] = -1;
cout << dp[len] << endl;
return 0;
}