[题解]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}) dn+i=1n1max(0,didi+1)
  • 证明略
  • 复杂度 O ( n ) 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) O(Tnai)

代码

#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 条边不相交的长度至少为 m i d mid mid 的链
  • f [ u ] f[u] f[u] 表示 u u u 的子树内最多能选出的链数
  • g [ u ] g[u] g[u] 表示 u u u 的子树内在选出链数最多的前提下,从 u u u 向下延伸的最长链长度
  • 一个结论:在全局最优方案中,对于任何一个 u u u u u u 的子树内选出的链数量需要达到最大值
  • 证明:如果 u u u 的子树内选出的链数量没有达到最大值,那么尝试把 u u u 的子树内的方案改成选出的链数更大的方案,那么 u u u 的子树外的贡献最多减 1 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)) f[u]=vson(u)f[v]+match(g[v]+len(u,v),vson(u))
  • l e n ( u , v ) len(u,v) len(u,v) 为边 ( 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)) match(g[v]+len(u,v),vson(u)) 表示有一个长度为 t o t tot tot 的数组 a a a
  • 其中 a a a 的第 i i i 个元素为 g [ v ] + l e n ( u , v ) g[v]+len(u,v) g[v]+len(u,v) v v v u u u 的第 i i i 个子节点)
  • m a t c h ( … &ThinSpace; ) match(\dots) match() 表示 a a a 数组中最多能分出多少个大小在 [ 1 , 2 ] [1,2] [1,2] 内的集合
  • 一个数能被分到一个集合,当且仅当这个数 ≥ m i d \ge mid mid
  • 两个数能被分到一个集合,当且仅当这两个数之和 ≥ m i d \ge mid mid
  • 然后你可以写一个二分图最大匹配做到 O(n^2logn) 的优秀复杂度
  • 考虑贪心匹配
  • 先把 a a a 排序
  • 先把 a a a 中不小于 m i d mid mid 的数各自独立分到一个集合
  • 然后小于 m i d mid mid 的数贪心两两匹配
  • 用 two-pointers l l l r r r ,初始 l = 1 , r = t l=1,r=t l=1,r=t t t t a a a 中小于 m i d mid mid 的数的个数)
  • 如果 a l + a r ≥ m i d a_l+a_r\ge mid al+armid 则匹配成功, l l l 加一, r r r 减一
  • 否则匹配失败, l l l 加一
  • 如果 l ≥ r l\ge r lr 则完成匹配
  • 最后一个问题: g g g 的转移
  • 显然 g [ u ] &lt; m i d g[u]&lt;mid g[u]<mid
  • 注意到我们上面的贪心匹配不能保证同样最优的情况下 a a a 内未匹配的元素最大
  • 设匹配成功的的对数为 k k k
  • 可以发现,一定存在一种最优方案是在 a a a 数组的前 2 k + 1 2k+1 2k+1 大的数中选 2 k 2k 2k 个数两两配对
  • 所以在 a a a 的前 2 k + 1 2k+1 2k+1 大的数中,枚举未匹配的数,判断剩下的数是否能匹配成功即可
  • 复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Tree(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)

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;
}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 5e4 + 5, M = N << 1;

int n, m, ecnt, nxt[M], adj[N], go[M], val[M], f[N], g[N], tot, tmp[N];
bool mark[N], lf[N], rf[N];

void add_edge(int u, int v, int w)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}

void dfs(int u, int fu, int mid)
{
	f[u] = 0;
	Tree(u) dfs(v, u, mid), f[u] += f[v];
	tot = 0;
	Tree(u) tmp[++tot] = g[v] + val[e];
	std::sort(tmp + 1, tmp + tot + 1);
	int i, ri = tot, p = tot, ac = 0;
	For (i, 1, tot) mark[i] = 0;
	while (ri && tmp[ri] >= mid) ri--, p--, f[u]++;
	For (i, 1, ri)
	{
		if (i >= p) break;
		if (tmp[i] + tmp[p] >= mid)
			mark[i] = 1, mark[p--] = 1, f[u]++, ac++;
	}
	g[u] = 0;
	For (i, 1, ri) if (!mark[i]) g[u] = tmp[i];
	if ((ac << 1) == ri) return;
	lf[0] = rf[0] = 1;
	For (i, 1, ac) lf[i] = lf[i - 1] &&
		tmp[ri - i + 1] + tmp[ri - (ac << 1) + i - 1] >= mid;
	For (i, 1, ac) rf[i] = rf[i - 1] &&
		tmp[ri - ac - i] + tmp[ri - ac + i - 1] >= mid;
	For (i, 0, ac) if (rf[ac - i] && lf[i])
		g[u] = Max(g[u], tmp[ri - i]);
}

bool check(int mid)
{
	return dfs(1, 0, mid), f[1] >= m;
}

int main()
{
	int i, x, y, z;
	n = read(); m = read();
	For (i, 1, n - 1)
		x = read(), y = read(), z = read(), add_edge(x, y, z);
	int l = 0, r = 500000000;
	while (l <= r)
	{
		int mid = l + r >> 1;
		if (check(mid)) l = mid + 1;
		else r = mid - 1;
	}
	std::cout << r << std::endl;
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值