2024.1.4做题纪要
P3628 [APIO2010] 特别行动队
斜率优化板子题。具体式子不说了QAQ。
记住当让 \(\frac{y_1 - y_2}{x_1 - x_2} \ge k\) 时维护上凸壳,当 \(\frac{y_1 - y_2}{x_1 - x_2} \leq k\) 时维护下凸壳。
还有当化简 \(dp\) 式子的时候,把纯常量提出来,只存纯变量,将与变量相乘的常量作为 \(k\)。
开心
#include <bits/stdc++.h>
typedef long long ll;
int N;
ll a, b, c;
ll value[1010000];
ll dp[1001000], sum[1001000];
// ll queue[1001000], l = 1, r = 0;
ll GetValue(ll val) {
return a * val * val + b * val + c;
}
double Slope(std::pair<ll, ll> &a, std::pair<ll, ll> &b) {
return 1.0 * (b.second - a.second) / (1.0 * (b.first - a.first));
}
class Monotonic_Queue {
public:
std::pair<ll, ll> number[1001000];
int id[1001000];
int l, r;
Monotonic_Queue() {
l = 1, r = 0;
}
bool less(std::pair<ll, ll> &a, std::pair<ll, ll> &b, std::pair<ll, ll> &c) {
return Slope(a, b) <= Slope(b, c);
}
bool greater(std::pair<ll, ll> &a, std::pair<ll, ll> &b, std::pair<ll, ll> &c) {
return Slope(a, b) >= Slope(b, c);
}
void pop(double k) {
while (l + 1 <= r && k < Slope(number[l], number[l + 1]))
l ++;
}
void emplace(ll x, ll y, int ID) {
std::pair<ll, ll> New = std::make_pair(x, y);
while (l + 1 <= r && less(number[r - 1], number[r], New))
r --;
id[++ r] = ID;
number[r] = New;
}
std::pair<ll, ll> front_pos() {
return number[l];
}
int front_id() {
return id[l];
}
}queue;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N;
std::cin >> a >> b >> c;
for (int i = 1; i <= N; ++ i) {
std::cin >> value[i];
sum[i] = sum[i - 1] + value[i];
}
queue.emplace(0, 0, 0);
for (int i = 1; i <= N; ++ i) {
double k = 2 * a * sum[i];
queue.pop(k);
int j = queue.front_id();
std::pair<ll, ll> temp = queue.front_pos();
dp[i] = temp.second - 2ll * a * sum[i] * sum[j] + 1ll * a * sum[i] * sum[i] + 1ll * b * sum[i] + c;
queue.emplace(sum[i], dp[i] + 1ll * a * sum[i] * sum[i] - 1ll * b * sum[i], i);
}
std::cout << dp[N] << '\n';
return 0;
}
省选联测6 耀眼
逆天根号分治。
不会讲,太菜了QAQ。
伤心
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
typedef long long ll;
const ll mod = 998244353;
const int blocks = 650;
const int S = 1023;
ll N, W;
int dp1[S + 10][1400], dp2[2][400100];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
#ifndef LOCAL_STDIO
freopen("dazzling.in", "r", stdin);
freopen("dazzling.out", "w", stdout);
#endif
std::cin >> N >> W;
if (N == 1) {
std::cout << 0 << '\n';
return 0;
}
for (int i = 1; i <= N; ++i) {
memset(dp1[i & S], 0, sizeof(dp1[i & S]));
if (i <= blocks)
dp1[i][i] = 1;
for (int j = 1; j <= 1000 && j <= i; ++j)
dp1[i & S][j] = (dp1[i & S][j] + W * dp1[(i - j) & S][j + 1] + dp1[(i - j) & S][j - 1]) % mod;
}
ll answer = 0;
for (int i = 1; i <= 1000; ++i)
answer = (answer + dp1[N & S][i]);
dp2[0][N] = 1;
bool flag = 1;
// answer = 0;
for (int i = 1; i * blocks <= 2 * N; ++i) {
memset(dp2[flag], 0, sizeof(int) * (N * 2));
for (int j = 0; j <= 2 * N; ++j) {
dp2[flag][j] = (dp2[flag][j] + W * (i + j <= 2 * N ? dp2[flag ^ 1][j + i] : 0) + (j - i >= 0 ? dp2[flag ^ 1][j - i] : 0)) % mod;
//注意从flag转移过来的
}
for (int j = blocks + 1; j <= N; ++j)
if (i * j <= 2 * N)
answer = (answer + dp2[flag ^ 1][N * 2 - i * j]);
//这里加 dp2[flag ^ 1] 的原因是:在当前这一位之后,剩下还要选的长度是flag^1滚之前的代表长度
flag ^= 1;
}
answer %= mod;
std::cout << answer << '\n';
return 0;
}
/*
5 2
*/
P3195 [HNOI2008] 玩具装箱
先列出 \(dp\) 式子:
\[dp[i] = \min_{j=0}^{i - 1} \{dp[j] + (i - (j + 1) + pre_i - pre_j - L)^2\}
\]
单独考虑 \(\min\) 里面的式子。
我们设 \(sum_i=pre_i + i\)
则式子变成:
\[dp_j+ (sum_i - sum_j - 1 - L) ^ 2
\]
我们再将 \(L\) 提前加上 \(1\)。
\[dp_j + (sum_i - sum_j - L) ^ 2
\]
开平方为
\[sum_i \cdot (sum_i - sum_j - L) - sum_j \cdot (sum_i - sum_j - L) - L \cdot (sum_i - sum_j - L)
\]
\[sum_i^2 - sum_i \cdot sum_j - sum_i \cdot L - sum_i \cdot sum_j + sum_j^2 + sum_j \cdot L - sum_i \cdot L + sum_j \cdot L + L^2
\]
归类:
\[sum_i^2 - sum_i \cdot L - sum_i \cdot L+ L^2 - sum_i \cdot sum_j - sum_i \cdot sum_j + sum_j^2 + sum_j \cdot L + sum_j \cdot L
\]
\[(sum_i - L) ^ 2 + sum_j^2 + 2 \cdot sum_j \cdot (L - sum_i)
\]
将 \((sum_i - L) ^ 2\) 提出,就成了下列式子:
\[sum_j^2 + 2 \cdot sum_j \cdot (L - sum_i)
\]
我们设 \(-k = 2 \cdot (L - sum_i)\),则式子变成了:
\[f(j)=dp_j+sum_j^2 - k \cdot sum_j
\]
我们设 \(j_1 < j_2\) 且 \(f(j_1) \ge f(j_2)\), 此时我们就会选 \(j_2\),那么:
\[dp_{j_1} + sum_{j_1}^2 - k \cdot sum_{j_1} \ge dp_{j_2} + sum_{j_2}^2 - k \cdot sum_{j_2}
\]
\[dp_{j_1} + sum_{j_1}^2 - (dp_{j_2} + sum_{j_2}^2) \ge k \cdot (sum_{j_1} - sum_{j_2})
\]
因为 \(sum_{j_1} - sum_{j_2}\) 一定大于 \(0\),所以当不为 \(0\) 的时候
\[\frac{dp_{j_1} + sum_{j_1}^2 - (dp_{j_2} + sum_{j_2}^2)}{sum_{j_1} - sum_{j_2}} \le k
\]
\[\frac{dp_{j_2} + sum_{j_2}^2 - (dp_{j_1} + sum_{j_1}^2)}{sum_{j_2} - sum_{j_1}} \le k
\]
激动
#include <bits/stdc++.h>
typedef long long ll;
int N;
ll L, dp[51000], C[51000], sum[51000];
class Monotonic_Queue {
public:
int l, r;
std::pair<ll, ll> number[51000];
Monotonic_Queue() {
l = 1, r = 0;
}
double Slope(std::pair<ll, ll> a, std::pair<ll, ll> &b) {
return 1ll * (b.second - a.second) / (1.0 * (b.first - a.first));
}
bool greater(std::pair<ll, ll> &a, std::pair<ll, ll> &b, std::pair<ll, ll> &c) {
return Slope(a, b) >= Slope(b, c);
}
void emplace(ll x, ll y) {
std::pair<ll, ll> result = std::make_pair(x, y);
while (l + 1 <= r && greater(number[r - 1], number[r], result))
r --;
number[++ r] = result;
}
void pop(double k) {
while (l + 1 <= r && k >= Slope(number[l], number[l + 1]))
l ++;
}
std::pair<ll, ll> front() {
return number[l];
}
void print() {
for (int i = l; i + 1 <= r; ++ i)
std::cout << Slope(number[i], number[i + 1]) << ' ';
std::cout << '\n';
}
}queue;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N >> L;
L ++;
for (int i = 1; i <= N; ++ i) {
std::cin >> C[i];
sum[i] = sum[i - 1] + C[i];
}
for (int i = 1; i <= N; ++ i)
sum[i] += i;
queue.emplace(0, 0);
for (int i = 1; i <= N; ++ i) {
int k = 2ll * (sum[i] - L);
queue.pop(k);
std::pair<ll, ll> temp = queue.front();
dp[i] = temp.second - 1ll * k * temp.first + (sum[i] - L) * (sum[i] - L);
queue.emplace(sum[i], dp[i] + sum[i] * sum[i]);
}
std::cout << dp[N] << '\n';
return 0;
}
P2120 [ZJOI2007] 仓库建设
其实跟上面的一样,不多BB了。但是该注意的是 \(p_i=0\) 的情况,当 \(p_i=0\) 时直接 \(dp_i=dp_{i-1}\),再与单调队列求出来的取 \(\min\) 就行了,其实一些数组不太一样,明天再说。
兴奋
#include <bits/stdc++.h>
typedef long long ll;
int N;
ll p[1100000], x[1100000], c[1100000];
ll partSum[1100000], costSum[1100000];
ll dp[1100000];
class Monotonic_Queue {
public:
int l, r;
std::pair<ll, ll> number[51000];
Monotonic_Queue() {
l = 1, r = 0;
}
double Slope(std::pair<ll, ll> a, std::pair<ll, ll> &b) {
return 1ll * (b.second - a.second) / (1.0 * (b.first - a.first));
}
bool greater(std::pair<ll, ll> &a, std::pair<ll, ll> &b, std::pair<ll, ll> &c) {
return Slope(a, b) >= Slope(b, c);
}
void emplace(ll x, ll y) {
std::pair<ll, ll> result = std::make_pair(x, y);
while (l + 1 <= r && greater(number[r - 1], number[r], result))
r --;
number[++ r] = result;
}
void pop(double k) {
while (l + 1 <= r && k >= Slope(number[l], number[l + 1]))
l ++;
}
std::pair<ll, ll> front() {
return number[l];
}
void print() {
for (int i = l; i + 1 <= r; ++ i)
std::cout << Slope(number[i], number[i + 1]) << ' ';
std::cout << '\n';
}
}queue;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N;
for (int i = 1; i <= N; ++ i) {
std::cin >> x[i] >> p[i] >> c[i];
partSum[i] = partSum[i - 1] + p[i];
dp[i] = 2e18;
}
for (int i = N - 1; i >= 0; -- i) {
costSum[i] = costSum[i + 1] + (x[N] - x[i + 1]) * p[i + 1];
}
dp[0] = 0;
queue.emplace(partSum[0], dp[0] + costSum[0]);
for (int i = 1; i <= N; ++ i) {
ll k = x[N] - x[i];
queue.pop(-k);
std::pair<ll, ll> temp = queue.front();
if (p[i] == 0)
dp[i] = dp[i - 1];
dp[i] = std::min(dp[i], temp.second + k * temp.first - costSum[i] - k * partSum[i] + c[i]);
queue.emplace(partSum[i], dp[i] + costSum[i]);
}
std::cout << dp[N] << '\n';
return 0;
}