1. 程式人生 > >NOIP2018提高組題解(附填數遊戲logn做法)

NOIP2018提高組題解(附填數遊戲logn做法)

總體來說,Day1的3題非常水,Day2的難度卻飆升到一定境界了……然後我就GG了……

T1 鋪設道路

題目連結
這道題一眼原題,顯然,如果 d i > d i

1 d_i>d_{i-1} ,那麼就會對答案造成 d i
d i 1 d_i-d_{i-1}
的貢獻,否則無貢獻。於是程式碼只有十行。

#include <bits/stdc++.h>
using namespace
std; int main() { int n, t, lst = 0, res = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &t); if (t > lst) res += t - lst; lst = t; } printf("%d\n", res); return 0; }

T2 貨幣系統

題目連結
這道題在考場上看見的時候有點慌,畢竟剛開始一點思路都沒有……後來猜測了一個很有道理的結論,最小選出的集合 B B 一定是原集合 A A 的子集。
證明:
反證法,如果不滿足,必然有至少一個數 k A k\notin A ,並且 k k 不能被 B B 中的其他數表示,並且 k k 一定可以被 A A 中的數表示(否則就多出了一個可以表示的數,不符合題意)。
考慮 A A 中可以表示 k k 且不存在於 B B 中的數的集合為 S S ,如果 S S 中的所有數字都可以被 B B 表示,那麼 k k 一定也可以被 B B 表示,矛盾;否則 B B 不能表示出 A A 中的某個數字,不符合題意。
綜上所述,選出集合一定是原集合的子集。
然後這道題就很簡單了, B B 要求能夠表示出 A A 中的所有數字,也就是說如果 A A 中某個數字可以被其它數字表示就刪掉,剩下的必須保留。這個東西把 A A 從小到大排個序跑一遍完全揹包就行了。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 105, maxm = 25005;
int f[maxm], arr[maxn], n, T;
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d", arr + i);
        sort(arr + 1, arr + 1 + n);
        memset(f, 0, sizeof(f));
        f[0] = 1;
        int res = 0;
        for (int i = 1; i <= n; i++) {
            if (f[arr[i]]) continue;
            res++;
            for (int j = arr[i]; j < maxm; j++)
                f[j] |= f[j - arr[i]];
        }
        printf("%d\n", res);
    }
    return 0;
}

T3 賽道修建

題目連結
顯然,題目要求最小化長度最長路徑的長度,肯定可以二分。之後就在於如何判定。假設當前二分的值為 m m ,我們對於樹進行一遍dfs,每個子樹儘量多地選路徑,選出的個數一樣多的情況下要求留給連到子樹根的沒用過的鏈最長。然後對於每個節點,它會掛著很多沒有用過的鏈,這個可以貪心選取鏈兩兩接起來(具體操作後面再說),並返回剩下的最長鏈。為什麼這樣是對的?如果子樹沒有儘量多地選路徑,但是可以返回一個更長的鏈怎麼辦?顯然這個不夠優秀。因為原來的鏈大不了就不選,答案最多減少1,而後面沒有儘量多地選路徑已經讓答案至少減少了1,因此貪心是正確的。
我們接下來考慮怎麼把鏈兩兩配對還能夠返回剩下鏈的最大值。我們把鏈從小到大排序,對於每一條鏈,我們去找最短的鏈,使得這兩條鏈的長度之和 m \ge m ,然後刪除這兩條鏈繼續尋找。我在考場上怕set被卡常,所以用連結串列實現的,類似two pointer的技巧排序之後掃一遍就行了(考場上怕出鍋寫了很多奇怪的東西,其實估計很多都不需要)。
然後這道題就在 O ( n l o g 2 n ) O(nlog^2n) 的複雜度內解決了,我相信CCF i7的CPU一定可以跑過去的!心中有理想,國家能富強
(好吧這份程式碼在洛谷上最慢的點40ms)

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> P;

const int maxn = 50005;
struct Edge { int to, val, next; } edge[maxn * 2];
int par[maxn], arr[maxn], ord[maxn], head[maxn];
int used[maxn], tot, n, m, lim;
P f[maxn];
void addedge(int u, int v, int w) {
	edge[++tot] = (Edge) { v, w, head[u] };
	head[u] = tot;
}
void efs(int u, int fa) {
	par[ord[++tot] = u] = fa;
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		if (v != fa) efs(v, u);
	}
}
int rgt[maxn], lft[maxn];
void del(int x) {
	lft[rgt[x]] = lft[x];
	rgt[lft[x]] = rgt[x];
	used[x] = 1;
}
int get_rgt(int x) {
	while (used[rgt[x]]) x = rgt[x];
	return rgt[x];
}
int get_lft(int x) {
	while (used[lft[x]]) x = lft[x];
	return lft[x];
}
int check(int mid) {
	memset(used, 0, sizeof(used));
	lim = mid;
	for (int i = tot; i > 0; i--) {
		int u = ord[i];
		int cnt = 0, nn = 0;
		for (int i = head[u]; i; i = edge[i].next) {
			int v = edge[i].to, w = edge[i].val;
			if (v == par[u]) continue;
			P p = f[v];
			cnt += p.first;
			arr[++nn] = p.second + w;
		}
		sort(arr + 1, arr + 1 + nn);
		arr[nn + 1] = 0;
		for (int i = 1; i <= nn; i++)
			rgt[i] = i + 1, lft[i] = i - 1;
		rgt[0] = 1, lft[nn + 1] = nn;
		for (int i = 0; i <= nn + 1; i++) used[i] = 0;
		for (int i = nn; i > 0; i--) if (arr[i] >= lim) del(i), ++cnt;
		for (int i = rgt[0], j = lft[nn + 1]; i < nn && rgt[i] <= nn;) {
			int t = arr[i];
			if (j <= i) j = rgt[i];
			while (lft[j] > i && arr[lft[j]] + t >= lim) j = lft[j];
			if (arr[j] + t < lim) { i = rgt[i]; continue; }
			del(i), del(j), ++cnt;
			i = get_rgt(i), j = get_rgt(j);
		}
		f[u] = P(cnt, arr[lft[nn + 1]]);
	}
	return f[1].first >= m;
}
int main() {
	scanf("%d%d", &n, &m);
	int sum = 0;
	for (int i = 1; i < n; i++) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		addedge(u, v, w);
		addedge(v, u, w);
		sum += w;
	}
	efs(1, tot = 0);
	int l = 0, r = sum / m + 10;
	while (l + 1 < r) {
		int mid = (l + r) >> 1;
		if (check(mid)) l = mid;
		else r = mid;
	}
	printf("%d\n", l);
	fclose(stdin), fclose(stdout);
	return 0;
}

T4 旅行

題目連結
本來是一道水題,我先開始沒看到 m n m\le n 的條件,後來又以為資料量1E5,白白浪費了20min……
樹肯定很簡單,每次貪心地往最小的兒子走即可;奇環樹就暴力剖環,然後當成樹跑一遍取最小值就行了。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5005, maxm = 10005;
vector<int> gra[maxn];
int head[maxn], vis[maxn], sta[maxn], isc[maxn], tot, n, m, ca, cb;
bool findc(int u, int fa) {
	vis[sta[++tot] = u] = 1;
	int s = gra[u].size();
	for (int i = 0; i < s; i++) {
		int v = gra[u][i];
		if (v == fa) continue;
		if (vis[v]) {
			int x = 0;
			while (x != v) isc[x = sta[tot--]] = 1;
			return true;
		}
		if (findc(v, u)) return true;
	}
	--tot;
	return false;
}
int temp[maxn], ans[maxn];
void dfs(int u, int fa) {
	temp[++tot] = u;
	int s = gra[u].size();
	for (int i = 0; i < s; i++)