(1) CF1941B Rudolf and 121
給定一個長度為 \(n\) 的序列 \(a\)。求最少進行多少次操作後所有 \(a_i = 0\):
- 選擇一個 \(2 \le i < n\),並讓 \(a_i \gets a_i - 2, a_{i - 1} \gets a_{i - 1} - 1, a_{i + 1} \gets a_{i + 1} - 1\)。
我們記選擇 \(i = x\) 時的操作為 \(\operatorname{opt}(x)\)。
發現 \(a_1\) 只有 \(\operatorname{opt}(2)\) 才會發生變化。也就是說我們只能透過若干次 \(\operatorname{opt}(2)\) 才能使 \(a_1 = 0\)。所以 \(\operatorname{opt}(2)\) 需要做 \(a_1\) 次。
此時 \(a_1 = 0\)。
同理,對於 \(a_2\),顯然我們不能選擇 \(\operatorname{opt}(1)\) 或 \(\operatorname{opt}(2)\)。因為這兩種操作都會使 \(a_1\) 變小。所以此時能影響到 \(a_2\) 的只有 \(\operatorname{opt}(3)\)。
同理依次操作 \(\operatorname{opt}(i), i = (2, 3, 4, \dots, n - 1)\)。然後判斷所有元素是否均為 \(0\) 即可。
時間複雜度 \(\Theta(n)\)。
程式碼
#include <bits/stdc++.h>
using namespace std;
//#define int long long
typedef long long ll;
typedef unsigned long long LL;
typedef pair<int, int> PII;
struct FASTREAD {
template <typename T>
FASTREAD& operator >>(T& x) {
x = 0; bool flg = false; char c = getchar();
while (c < '0' || c > '9') flg |= (c == '-'), c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
if (flg) x = -x; return *this;
}
template <typename T>
FASTREAD& operator >>(vector<T>& x) {
for (auto it = x.begin(); it != x.end(); ++ it ) (*this) >> *it;
return *this;
}
}fin;
struct FASTWRITE {
template <typename T>
FASTWRITE& operator <<(T x) {
if (x < 0) x = -x, putchar('-');
static int buf[35]; int top = 0;
do buf[top ++ ] = x % 10, x /= 10; while (x);
while (top) putchar(buf[ -- top] + '0');
return *this;
}
FASTWRITE& operator <<(char x) {
putchar(x); return *this;
}
template <typename T>
FASTWRITE& operator <<(vector<T> x) {
for (auto it = x.begin(); it != x.end(); ++ it ) (*this) << *it, putchar(' ');
putchar('\n');
return *this;
}
}fout;
const int N = 1e6 + 10;
const int P = 998244353;
void Luogu_UID_748509() {
int n; fin >> n;
vector<int> a(n);
fin >> a;
for (int i = 0; i + 2 < n; ++ i ) {
if (a[i] < 0) {
puts("NO");
return;
}
a[i + 1] -= a[i] * 2;
a[i + 2] -= a[i];
a[i] = 0;
}
puts(a[n - 2] || a[n - 1] ? "NO" : "YES");
}
signed main() {
int Testcases = 1;
fin >> Testcases;
while (Testcases -- ) Luogu_UID_748509();
return 0;
}
(2) CF1941D Rudolf and the Ball Game
有 \(n\) 個人圍成一圈。最開始第 \(x\) 個人拿著一個球。
接下來會發生 \(m\) 個事件,每個事件形如 \((r_i, c_i)\),其中:
- \(c_i = \texttt 0\) 表示當前手中有球的人將球順時針傳給第 \(x_i\) 個人。
- \(c_i = \texttt 1\) 表示當前手中有球的人將球逆時針傳給第 \(x_i\) 個人。
- \(c_i = \texttt ?\) 表示這個事件不清楚,當前手中有球的人將球順時針或逆時針傳給第 \(x_i\) 個人。
求最終哪些人可能有球。
我們設 bool 狀態 \(f_{i, j}\) 表示第 \(i\) 個事件結束後,第 \(j\) 個人是否有可能拿著球。開始 \(f_{0, x} = \text{true}\)。
轉移極易,我們令 \(F(x, y)\) 表示從 \(x\) 順時針第 \(y\) 個人的編號,\(G(x, y)\) 表示從 \(x\) 逆時針第 \(y\) 個人的編號。那麼有
最後看哪些 \(j\) 滿足 \(f_{n, j} = \text{true}\) 即可。
時空複雜度均 \(\Theta(nm)\)。當然可以滾動陣列將空間複雜度最佳化成 \(\Theta(m)\)。
程式碼
#include <bits/stdc++.h>
using namespace std;
//#define int long long
typedef long long ll;
typedef unsigned long long LL;
typedef pair<int, int> PII;
struct FASTREAD {
template <typename T>
FASTREAD& operator >>(T& x) {
x = 0; bool flg = false; char c = getchar();
while (c < '0' || c > '9') flg |= (c == '-'), c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
if (flg) x = -x; return *this;
}
template <typename T>
FASTREAD& operator >>(vector<T>& x) {
for (auto it = x.begin(); it != x.end(); ++ it ) (*this) >> *it;
return *this;
}
}fin;
struct FASTWRITE {
template <typename T>
FASTWRITE& operator <<(T x) {
if (x < 0) x = -x, putchar('-');
static int buf[35]; int top = 0;
do buf[top ++ ] = x % 10, x /= 10; while (x);
while (top) putchar(buf[ -- top] + '0');
return *this;
}
FASTWRITE& operator <<(char x) {
putchar(x); return *this;
}
template <typename T>
FASTWRITE& operator <<(vector<T> x) {
for (auto it = x.begin(); it != x.end(); ++ it ) (*this) << *it, putchar(' ');
putchar('\n');
return *this;
}
}fout;
const int N = 1e6 + 10;
const int P = 998244353;
int n, m, x;
int dis;
char op;
bool st[110][N];
int F(int a, int b) {
return ((a + b) % n + n) % n;
}
void Luogu_UID_748509() {
fin >> n >> m >> x;
// for (int j = 0; j <= m; ++ j )
for (int i = 0; i < n; ++ i )
st[0][i] = st[1][i] = 0;
st[0][x - 1] = true;
for (int j = 1; j <= m; ++ j ) {
cin >> dis >> op;
for (int i = 0; i < n; ++ i )
if (st[j - 1 & 1][i]) {
if (op != '1') st[j & 1][F(i, dis)] = true;
if (op != '0') st[j & 1][F(i, -dis)] = true;
st[j - 1 & 1][i] = 0;
}
}
int res = 0;
for (int i = 0; i < n; ++ i ) res += st[m & 1][i];
fout << res << '\n';
for (int i = 0; i < n; ++ i ) if (st[m & 1][i]) fout << i + 1 << ' ';
puts("");
}
signed main() {
int Testcases = 1;
fin >> Testcases;
while (Testcases -- ) Luogu_UID_748509();
return 0;
}
(3) CF1941E Rudolf and k Bridges
有一條 \(n \times m\) 的河。第 \(i\) 行第 \(j\) 列的深度為 \(a_{i, j}\)。保證 \(a_{i, 1} = a_{i, m} = 0\)。
如果在第 \(i\) 行第 \(j\) 列安置橋墩,所需代價為 \(a_{i, j} + 1\)。
你需要選擇連續的 \(k\) 行,每行都要架起若干個橋墩,並滿足以下條件:
- 每行的第 \(1\) 列必須架橋墩;
- 每行的第 \(m\) 列必須架橋墩;
- 每行的相鄰兩個橋墩的距離不超過 \(d\)。其中 \((i, j_1)\) 和 \((i, j_2)\) 之間的距離為 \(|j_1 - j_2| - 1\)。
求最小代價和。
行與行之間架橋墩並無關係。我們可以求出第 \(i\) 行所需的最小代價 \(g(i)\),那麼 \(\min_{i=1}^{n-k+1}\sum_{j=i}^{i+k-1}g(j)\) 即為答案。
對於每一行分別 DP,當前是第 \(r\) 行。設狀態 \(f(i)\) 表示若只考慮前 \(i\) 列,且第 \(i\) 列一定架橋,所需的最小代價和。轉移列舉上一個橋墩的位置,即:
發現後面的 \(\min_{j=i-k-1}^{i - 1} f(j)\) 可以非常容易地用資料結構維護,例如單調佇列或線段樹。
總時間複雜度為 \(\Theta(nm\log m)\),用線段樹實現的。
程式碼
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef unsigned long long LL;
typedef pair<int, int> PII;
struct FASTREAD {
template <typename T>
FASTREAD& operator >>(T& x) {
x = 0; bool flg = false; char c = getchar();
while (c < '0' || c > '9') flg |= (c == '-'), c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
if (flg) x = -x; return *this;
}
template <typename T>
FASTREAD& operator >>(vector<T>& x) {
for (auto it = x.begin(); it != x.end(); ++ it ) (*this) >> *it;
return *this;
}
}fin;
struct FASTWRITE {
template <typename T>
FASTWRITE& operator <<(T x) {
if (x < 0) x = -x, putchar('-');
static int buf[35]; int top = 0;
do buf[top ++ ] = x % 10, x /= 10; while (x);
while (top) putchar(buf[ -- top] + '0');
return *this;
}
FASTWRITE& operator <<(char x) {
putchar(x); return *this;
}
template <typename T>
FASTWRITE& operator <<(vector<T> x) {
for (auto it = x.begin(); it != x.end(); ++ it ) (*this) << *it, putchar(' ');
putchar('\n');
return *this;
}
}fout;
const int N = 110, M = 2e5 + 10;
const int P = 998244353;
int n, m, k, d;
int a[N][M];
int sum[N];
int f[M];
struct Tree {
int l, r, v;
}tr[M << 2];
void pushup(int u) {
tr[u].v = min(tr[u << 1].v, tr[u << 1 | 1].v);
}
void build(int u, int l, int r) {
tr[u] = {l, r, 0};
if (l == r) tr[u].v = 0;
else {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
}
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(u << 1, x, d);
else modify(u << 1 | 1, x, d);
pushup(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 = 1e18;
if (l <= mid) res = query(u << 1, l, r);
if (r > mid) res = min(res, query(u << 1 | 1, l, r));
return res;
}
void Luogu_UID_748509() {
fin >> n >> m >> k >> d;
for (int i = 1; i <= n; ++ i ) {
for (int j = 1; j <= m; ++ j ) fin >> a[i][j];
build(1, 1, m);
f[1] = a[i][1] + 1;
modify(1, 1, f[1]);
for (int j = 2; j <= m; ++ j ) {
f[j] = a[i][j] + 1 + query(1, max(1ll, j - d - 1), j - 1);
modify(1, j, f[j]);
}
sum[i] = sum[i - 1] + f[m];
}
int res = 1e18;
for (int l = 1, r = k; r <= n; ++ l, ++ r ) res = min(res, sum[r] - sum[l - 1]);
fout << res << '\n';
return;
}
signed main() {
int Testcases = 1;
fin >> Testcases;
while (Testcases -- ) Luogu_UID_748509();
return 0;
}