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=1∑n−1max(0,di−di+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]=v∈son(u)∑f[v]+match(g[v]+len(u,v),v∈son(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),v∈son(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 ( …   ) 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+ar≥mid 则匹配成功, l l l 加一, r r r 减一
- 否则匹配失败, l l l 加一
- 如果 l ≥ r l\ge r l≥r 则完成匹配
- 最后一个问题: g g g 的转移
- 显然 g [ u ] < m i d g[u]<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;
}