NOIP2018提高組題解(附填數遊戲logn做法)
總體來說,Day1的3題非常水,Day2的難度卻飆升到一定境界了……然後我就GG了……
T1 鋪設道路
題目連結
這道題一眼原題,顯然,如果
,那麼就會對答案造成
的貢獻,否則無貢獻。於是程式碼只有十行。
#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 貨幣系統
題目連結
這道題在考場上看見的時候有點慌,畢竟剛開始一點思路都沒有……後來猜測了一個很有道理的結論,最小選出的集合
一定是原集合
的子集。
證明:
反證法,如果不滿足,必然有至少一個數
,並且
不能被
中的其他數表示,並且
一定可以被
中的數表示(否則就多出了一個可以表示的數,不符合題意)。
考慮
中可以表示
且不存在於
中的數的集合為
,如果
中的所有數字都可以被
表示,那麼
一定也可以被
表示,矛盾;否則
不能表示出
中的某個數字,不符合題意。
綜上所述,選出集合一定是原集合的子集。
然後這道題就很簡單了,
要求能夠表示出
中的所有數字,也就是說如果
中某個數字可以被其它數字表示就刪掉,剩下的必須保留。這個東西把
從小到大排個序跑一遍完全揹包就行了。
#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 賽道修建
題目連結
顯然,題目要求最小化長度最長路徑的長度,肯定可以二分。之後就在於如何判定。假設當前二分的值為
,我們對於樹進行一遍dfs,每個子樹儘量多地選路徑,選出的個數一樣多的情況下要求留給連到子樹根的沒用過的鏈最長。然後對於每個節點,它會掛著很多沒有用過的鏈,這個可以貪心選取鏈兩兩接起來(具體操作後面再說),並返回剩下的最長鏈。為什麼這樣是對的?如果子樹沒有儘量多地選路徑,但是可以返回一個更長的鏈怎麼辦?顯然這個不夠優秀。因為原來的鏈大不了就不選,答案最多減少1,而後面沒有儘量多地選路徑已經讓答案至少減少了1,因此貪心是正確的。
我們接下來考慮怎麼把鏈兩兩配對還能夠返回剩下鏈的最大值。我們把鏈從小到大排序,對於每一條鏈,我們去找最短的鏈,使得這兩條鏈的長度之和
,然後刪除這兩條鏈繼續尋找。我在考場上怕set被卡常,所以用連結串列實現的,類似two pointer的技巧排序之後掃一遍就行了(考場上怕出鍋寫了很多奇怪的東西,其實估計很多都不需要)。
然後這道題就在
的複雜度內解決了,我相信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 旅行
題目連結
本來是一道水題,我先開始沒看到
的條件,後來又以為資料量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++)