1. 程式人生 > >[題解]NOIP2018 Day1 Solution - by xyz32768

[題解]NOIP2018 Day1 Solution - by xyz32768

Rifkunatif thia ofa ck nweed ninck ova def
Epalts n ikwa offnet nazrot la pa
Olivazes unatifa ah kfafc fffak fjakfg
Orz zzq ak ioi
(火星文)
——《Ydjadf fha de NOIP 2018》

Day 1 T1 鋪設道路 road

演算法:模擬

  • NOIP 2013 原題
  • 答案為
  • d
    n + i = 1 n
    1
    max ( 0 , d
    i
    d i + 1 ) d_n+\sum_{i=1}^{n-1}\max(0,d_i-d_{i+1})
  • 證明略
  • 複雜度 O ( n ) O(n)

程式碼

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

typedef long long ll;

const int N = 1e5 + 5;

int n, a[N];

ll ans;

int main()
{
	int i;
	n = read();
	For (i, 1, n) a[i] = read();
	ans = a[n];
	For (i, 1, n - 1) if (a[i] > a[i + 1])
		ans += a[i] - a[i + 1];
	std::cout << ans << std::endl;
	return 0;
}

Day1 T2 貨幣系統 money

演算法:完全揹包

  • 顯然,如果一種貨幣會被面額小於它自己的貨幣表出,那麼這種貨幣就沒有使用的必要
  • 所以將所有貨幣按照面額為關鍵字從小到大排序後,進行完全揹包 DP
  • 如果某種貨幣不能被它之前的貨幣表出,則統計入答案
  • 否則這種貨幣沒有使用的必要
  • 複雜度 O ( T n a i ) O(Tna_i)

程式碼

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

const int N = 105, M = 25005;

int n, a[N], ans;
bool f[M];

void work()
{
	int i, j;
	n = read();
	For (i, 1, n) a[i] = read();
	std::sort(a + 1, a + n + 1);
	memset(f, 0, sizeof(f));
	f[0] = 1;
	ans = 0;
	For (i, 1, n)
	{
		if (!f[a[i]]) ans++;
		For (j, a[i], 25000)
			f[j] |= f[j - a[i]];
	}
	printf("%d\n", ans);
}

int main()
{
	int T = read();
	while (T--) work();
	return 0;
}

Day1 T3 賽道修建 track

演算法:二分答案 + DP + 貪心

  • 看到最小化最大值,很直觀地想到二分
  • 問題轉化成是否能在樹上選出至少 m m 條邊不相交的長度至少為 m i d mid 的鏈
  • f [ u ] f[u] 表示 u u 的子樹內最多能選出的鏈數
  • g [ u ] g[u] 表示 u u 的子樹內在選出鏈數最多的前提下,從 u u 向下延伸的最長鏈長度
  • 一個結論:在全域性最優方案中,對於任何一個 u u u u 的子樹內選出的鏈數量需要達到最大值
  • 證明:如果 u u 的子樹內選出的鏈數量沒有達到最大值,那麼嘗試把 u u 的子樹內的方案改成選出的鏈數更大的方案,那麼 u u 的子樹外的貢獻最多減 1 1 ,這樣答案一定不會更劣
  • 於是轉移方程出來了
  • f [ u ] = v s o n ( u ) f [ v ] + m a t c h ( g [ v ] + l e n ( u , v ) , v s o n ( u ) ) f[u]=\sum_{v\in son(u)}f[v]+match(g[v]+len(u,v),v\in son(u))
  • l e n ( u , v ) len(u,v) 為邊 ( u , v ) (u,v) 的長度
  • m a t c h ( g [ v ] + l e n ( u , v ) , v s o n ( u ) ) match(g[v]+len(u,v),v\in son(u)) 表示有一個長度為 t o t tot 的陣列 a a
  • 其中 a a 的第 i i 個元素為 g [ v ] + l e n ( u , v ) g[v]+len(u,v) v v u u 的第 i i 個子節點)
  • m a t c h ( &ThinSpace; ) match(\dots) 表示 a a 陣列中最多能分出多少個大小在 [ 1 , 2 ] [1,2] 內的集合
  • 一個數能被分到一個集合,當且僅當這個數 m i d \ge mid
  • 兩個數能被分到一個集合,當且僅當這兩個數之和 m i d \ge mid
  • 然後你可以寫一個二分圖最大匹配做到 O(n^2logn) 的優秀複雜度
  • 考慮貪心匹配
  • 先把 a a 排序
  • 先把 a a 中不小於 m i d mid 的數各自獨立分到一個集合
  • 然後小於 m i d mid 的數貪心兩兩匹配
  • 用 two-pointers l l r r ,初始 l = 1 , r = t l=1,r=t t t a a 中小於 m i d mid 的數的個數)
  • 如果 a l + a r m i d a_l+a_r\ge mid