1. 程式人生 > 其它 >NOIP 計劃 · 模擬賽 #10

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

題解