NOIP 計劃 · 模擬賽 #10
寫在前面
\(T1\) 簽到題。
\(T2\) 本以為什麼神仙題(正解確實很神仙),但簡單的二分貪心就能過掉, \kk。
\(T3\) 想到正解不會寫就很悲,打的暴力
\(T4\) 神仙博弈論
A 題麼麼
solution
考試時想到此題正解,語言功能出現障礙,但表示不會描述此題做法。
儘量說一下吧。
就是兩條相鄰的橫線一定會貫穿所有縱線,開兩個 map 一個記錄橫線相鄰兩個顏色成對出現的次數,一個記錄縱線相鄰兩個顏色成對出現的次數,然後對於每次詢問,兩個一乘就是答案,對於只有 \(1\) 滿足條件的特殊處理一下編號就好了。
複雜度 \(O(2n + k)\), \(map\) 可能還會帶個 \(log\)
code
/* work by:Ariel_ */ #include <iostream> #include <cstdio> #include <queue> #include <cstring> #include <map> #include <algorithm> using namespace std; const int MAXN = 1e5 + 5; int read() { int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();} return x * f; } int n, m, k, col[MAXN], tmp[MAXN]; map<int, map<int, int> >mp_1, mp_2, id_1, id_2; int main(){ n = read(), m = read(), k = read(); for (int i = 1; i <= n + 1; i++) { col[i] = read(); if(i != 1) mp_1[col[i - 1]][col[i]]++; } for (int i = 1; i <= n + 1; i++) { tmp[i] = read(); if(i != 1) { mp_2[tmp[i - 1]][tmp[i]]++; } } for (int i = 2; i <= n + 1; i++) { if(mp_1[col[i - 1]][col[i]] == 1) id_1[col[i - 1]][col[i]] = i - 1; if(mp_2[tmp[i - 1]][tmp[i]] == 1) id_2[tmp[i - 1]][tmp[i]] = i - 1; } for (int i = 1; i <= k; i++) { int a = read(), b = read(), c = read(), d = read(); if(mp_1[a][b] == 1 && mp_2[c][d] == 1) { cout<<id_1[a][b]<<" " << id_2[c][d]<<"\n"; continue; } printf("%d\n", mp_1[a][b] * mp_2[c][d]); } system("pause"); return 0; }
B. 題迷迷
題目描述
給定正整數 \(M, k\) 和一個長度為 \(n\) 的單調不下降序列 \(\{a\}\),你需要找到滿足如下要求的序列 \(\{b\}\):
-
\(b_1 \geq 0\)
-
\(b_n \leq M\)
-
對於任意的 \(1 \leq i < n, b_{i + 1} \geq b_i + 2k\)
最小化 \(\max\{|a_i - b_i|\}\)。 資料保證這樣的 \(\{b\}\) 存在。可以證明這個最小值一定是 \(\frac{1}{2}\) 的整數倍,所以直接輸出答案的 2 倍。
資料範圍
\(1 \leq n \leq 10^5, 1 \leq k \leq M \leq 10^8, 0 \leq a_i \leq M\)
solution
正解好像很麻煩的樣子。
直接二分列舉最小距離,然後判斷合不合法就好了。
為了避免實數二分,因為答案一定是 \(\frac{1}{2}\) 的整數倍,直接將 \(a\), \(b\) 序列乘以 \(2\) 就好了,注意 \(k\) 和 \(m\) 也要乘。
\(b[i] = \max\{b[i - 1] + 2k, a[i] - mid\}\), 判斷一下合不合法就好了。
特殊的 \(b[1] = \max{0, a[1] - v}\)
複雜度 \(O(nlog2m)\)
code
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <map>
#include <algorithm>
#define int long long
using namespace std;
const double eps = 1e-6;
const int MAXN = 1e5 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, m, k, b[MAXN], a[MAXN], Ans;
bool check(int mid) {
b[1] = max(0ll, a[1] - mid);
for (int i = 2; i <= n; i++) {
b[i] = b[i - 1] + k;
if(b[i] < a[i] - mid) b[i] = a[i] - mid;
else if(b[i] > a[i] + mid) return false;
if(b[i] > m) return false;
}
return true;
}
signed main(){
n = read(), m = read() * 2, k = read() * 4;
for (int i = 1; i <= n; i++) a[i] = read() * 2;
int l = 0, r = m;
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) Ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%lld", Ans);
system("pause");
return 0;
}
T3
原題和這個題只是稍微改一下不等號的方向就好了。
solution
線段樹合併維護dp
設 \(f_{u,i}\)表示 \(u\) 的子樹中的方案最大值,保證這個方案中點權最小值為 \(i\),當不選 \(u\) 時,依如下遞推式單次將 \(u\) 的集合(指之前掃過的兒子和自己構成的集合)與 \(v\) 的子樹的 \(dp\) 資訊合併(不存在方案設為 \(0\infty\))
\(f_{new} = \max_{j \geq i}\{f_{u, i} + \max{v, j}, f(v, i) + \max_{j \geq i} f(u, j)\}\)
當選 \(u\) 時,依遞推式
\(f(u, a_u) \leftarrow 1 + \sum_v max_{j \geq a_u} f(v, j)\)
線段樹合併,我們將不存在方案的 \(dp\) 在實現是設為 \(0\) (也就是這裡沒有節點),選 \(u\) 時是單點改比較正常,線上段樹合併(將 \(o'\) 合併到 \(o\) 上)時需要維護字尾 \(max\),如果 \(o=\text{null}\) 時,需要一個區間加操作,且一開始為 \(0\) 的位置不能被加。所以要儲存一個節點的 \(dp\) 最大值和有 \(dp\) 值的位置個數。時間複雜度 \(O(n\log n)\)。
code
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <map>
#include <algorithm>
using namespace std;
const int MAXN = 200005;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int N, a[MAXN], id[MAXN], rk[MAXN];
bool cmp(int x, int y) {return a[x] < a[y];}
struct edge{int v, nxt;}e[MAXN << 1];
int E = 1, head[MAXN];
void add_edge(int u, int v) {
e[++E] = (edge){v, head[u]};
head[u] = E;
}
int rt[MAXN];
namespace Seg {
int ls[MAXN * 30], rs[MAXN * 30], mx[MAXN * 30], tag[MAXN * 30], Tlen;
#define mid ((l + r) >> 1)
void pushUp(int o) { mx[o] = max(mx[ls[o]], mx[rs[o]]); }
void add(int o, int k) { if (mx[o]) mx[o] += k, tag[o] += k; }
void pushDown(int o) { if (tag[o]) add(ls[o], tag[o]), add(rs[o], tag[o]), tag[o] = 0; }
int query(int& o, int l, int r, int L, int R) {
if (!o) return 0;
if (l == L && r == R) return mx[o];
else {
pushDown(o);
if (R <= mid) return query(ls[o], l, mid, L, R);
else if (L > mid) return query(rs[o], mid + 1, r, L, R);
else return max(query(ls[o], l, mid, L, mid), query(rs[o], mid + 1, r, mid + 1, R));
}
}
void insert(int& o, int l, int r, int pos, int k) {
if (!o) o = ++Tlen;
if (l == r) {
mx[o] = max(mx[o], k);
}
else {
pushDown(o);
if (pos <= mid) insert(ls[o], l, mid, pos, k);
else insert(rs[o], mid + 1, r, pos, k);
pushUp(o);
}
}
void merge(int& o, int l, int r, int old, int mx1, int mx2) {
if (!o) o = old, add(o, mx1);
else if (!old) add(o, mx2);
else if (l == r) {
int val = max(mx[o] + mx[old], max (mx[o] + mx2, mx[old] + mx1));
mx[o] = val;
}
else {
pushDown(o), pushDown(old);
merge(ls[o], l, mid, ls[old], max(mx1, mx[rs[o]]), max(mx2, mx[rs[old]]));
merge(rs[o], mid + 1, r, rs[old], mx1, mx2);
pushUp(o);
}
}
}
using namespace Seg;
void dfs(int u, int ff) {
int qaq = 1;
for (int i = head[u]; i; i = e[i].nxt) if (e[i].v != ff) dfs(e[i].v, u), qaq += query(rt[e[i].v], 1, N, rk[u], N);
bool flag = 1;
for (int i = head[u]; i; i = e[i].nxt) if (e[i].v != ff) {
if (flag) rt[u] = rt[e[i].v], flag = 0;
else merge(rt[u], 1, N, rt[e[i].v], 0, 0);
}
insert(rt[u], 1, N, rk[u], qaq);
}
int ans;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", &a[i]), id[i] = i;
sort(id + 1, id + N + 1, cmp);
for (int i = 1; i <= N; ++i) rk[id[i]] = rk[id[i - 1]] + (a[id[i - 1]] != a[id[i]]);
for (int i = 2, x; i <= N; ++i) scanf("%d", &x), add_edge(x, i);
dfs(1, 0);
printf("%d\n", query(rt[1], 1, N, 1, N));
system("pause");
return 0;
}
T4
題面描述
是一個麻將大師,你正在學博弈論。
博弈的規則是這樣的:有 \(k\) 種不同的牌,每種有四張,其中有一些已經放在了桌子上。兩個人輪流操作,任選一張牌放到桌子上(注意不能讓桌子上的一種牌超過四張)。如果某個人操作後使桌子上的牌“和了”,則這個人輸,另一個人勝。
關於“和了”的定義,分為兩種情況:如果採用規則“無七對子”,“和了”需要保證有一種牌至少 \(2\) 張,另外不同的四種牌每種至少 \(3\) 張。如果採用規則“有七對子”,“和了”需要滿足上一條的定義,或者有七種不同的牌每種至少 \(2\) 張。
雙方都採用最優策略。給定開始時桌子上牌的狀態,你需要求出在這一狀態下,先手應該如何操作才能必勝。
資料範圍
\(T\leq 10^4, 5 \leq k \leq 10^9, p \in \{0, 1\}\)
挖大坑 /kk