文章目录

T1 铺设道路

​题目点击→洛谷 P5019 铺设道路​​​ 题目描述
春春是一名道路工程师,负责铺设一条长度为 nn 的道路。

铺设道路的主要工作是填平下陷的地表。整段道路可以看作是 2018 NOIP 提高组 复赛 day1_复赛 块首尾相连的区域,一开始,第 2018 NOIP 提高组 复赛 day1_#include_02 块区域下陷的深度为 2018 NOIP 提高组 复赛 day1_复赛_03

春春每天可以选择一段连续区间 2018 NOIP 提高组 复赛 day1_noip_04 ,填充这段区间中的每块区域,让其下陷深度减少 2018 NOIP 提高组 复赛 day1_提高组_05。在选择区间时,需要保证,区间内的每块区域在填充前下陷深度均不为 2018 NOIP 提高组 复赛 day1_2018_06

春春希望你能帮他设计一种方案,可以在最短的时间内将整段道路的下陷深度都变为 2018 NOIP 提高组 复赛 day1_2018_06

输入输出格式
输入格式:
输入文件包含两行,第一行包含一个整数 2018 NOIP 提高组 复赛 day1_复赛,表示道路的长度。 第二行包含 2018 NOIP 提高组 复赛 day1_复赛 个整数,相邻两数间用一个空格隔开,第 2018 NOIP 提高组 复赛 day1_#include_02 个整数为 2018 NOIP 提高组 复赛 day1_复赛_03

输出格式:
输出文件仅包含一个整数,即最少需要多少天才能完成任务。

输入输出样例
输入样例#1:
6
4 3 2 5 3 5
输出样例#1:
9

【样例解释】

一种可行的最佳方案是,依次选择:2018 NOIP 提高组 复赛 day1_#include_122018 NOIP 提高组 复赛 day1_#include_122018 NOIP 提高组 复赛 day1_复赛_142018 NOIP 提高组 复赛 day1_#include_152018 NOIP 提高组 复赛 day1_noip_162018 NOIP 提高组 复赛 day1_2018_172018 NOIP 提高组 复赛 day1_2018_172018 NOIP 提高组 复赛 day1_复赛_192018 NOIP 提高组 复赛 day1_复赛_19

【数据规模与约定】

对于 2018 NOIP 提高组 复赛 day1_noip_21 的数据,2018 NOIP 提高组 复赛 day1_2018_22
对于 2018 NOIP 提高组 复赛 day1_复赛_23 的数据,2018 NOIP 提高组 复赛 day1_2018_24
对于 2018 NOIP 提高组 复赛 day1_复赛_25 的数据,2018 NOIP 提高组 复赛 day1_提高组_26

T1 分析

常规送分题。考虑第一个位置的,这个位置是 2018 NOIP 提高组 复赛 day1_复赛_27 ,那么这个位置必定要被填 2018 NOIP 提高组 复赛 day1_复赛_27 次才能填平,然后考虑第二个位置,这个位置是 2018 NOIP 提高组 复赛 day1_2018_29 ,显然如果这个位置比 2018 NOIP 提高组 复赛 day1_复赛_27 小,那么在填 2018 NOIP 提高组 复赛 day1_复赛_27 的时候 2018 NOIP 提高组 复赛 day1_2018_29 也可以被填平,如果这个位置比 2018 NOIP 提高组 复赛 day1_复赛_27 大,那么显然 2018 NOIP 提高组 复赛 day1_2018_29 中的 2018 NOIP 提高组 复赛 day1_复赛_27 部分已经可以被填掉了,那么只要多加 2018 NOIP 提高组 复赛 day1_复赛_36 次就可以填平 2018 NOIP 提高组 复赛 day1_2018_29

所以做法就出来了,从前往后填坑,保证到第 2018 NOIP 提高组 复赛 day1_#include_02 个位置的时候,前2018 NOIP 提高组 复赛 day1_提高组_39个都被填满了,那么显然第 2018 NOIP 提高组 复赛 day1_#include_02 个位置中高度为2018 NOIP 提高组 复赛 day1_复赛_41的部分是不需要被填的,只需要填超出2018 NOIP 提高组 复赛 day1_复赛_41的部分就可以了。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int main()
{
int n;cin>>n;
int ans = 0,now = 0;
for (int i = 0 ; i < n ; ++i){
int x;cin>>x;
ans+= max(0,x - now);
now = x;
}
cout<<ans<<endl;
return 0;
}

T2 货币系统

​题目点击→洛谷 P5020 货币系统​​​ 题目描述
在网友的国度中共有 2018 NOIP 提高组 复赛 day1_复赛 种不同面额的货币,第 2018 NOIP 提高组 复赛 day1_#include_02 种货币的面额为 2018 NOIP 提高组 复赛 day1_提高组_45,你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 2018 NOIP 提高组 复赛 day1_复赛、面额数组为 2018 NOIP 提高组 复赛 day1_复赛_47 的货币系统记作 2018 NOIP 提高组 复赛 day1_复赛_48

在一个完善的货币系统中,每一个非负整数的金额 2018 NOIP 提高组 复赛 day1_#include_49 都应该可以被表示出,即对每一个非负整数 2018 NOIP 提高组 复赛 day1_#include_49,都存在 2018 NOIP 提高组 复赛 day1_复赛 个非负整数 2018 NOIP 提高组 复赛 day1_2018_52 满足 2018 NOIP 提高组 复赛 day1_noip_53 的和为 2018 NOIP 提高组 复赛 day1_#include_49。然而, 在网友的国度中,货币系统可能是不完善的,即可能存在金额 2018 NOIP 提高组 复赛 day1_#include_49 不能被该货币系统表示出。例如在货币系统 2018 NOIP 提高组 复赛 day1_noip_56, 2018 NOIP 提高组 复赛 day1_复赛_57 中,金额 2018 NOIP 提高组 复赛 day1_noip_58

两个货币系统 2018 NOIP 提高组 复赛 day1_复赛_482018 NOIP 提高组 复赛 day1_复赛_60 是等价的,当且仅当对于任意非负整数 2018 NOIP 提高组 复赛 day1_#include_49,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。他们希望找到一个货币系统 2018 NOIP 提高组 复赛 day1_复赛_60,满足 2018 NOIP 提高组 复赛 day1_复赛_60 与原来的货币系统 2018 NOIP 提高组 复赛 day1_复赛_48 等价,且 2018 NOIP 提高组 复赛 day1_提高组_65 尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的 2018 NOIP 提高组 复赛 day1_提高组_65

输入输出格式
输入格式:
输入文件的第一行包含一个整数 2018 NOIP 提高组 复赛 day1_2018_67,表示数据的组数。

接下来按照如下格式分别给出 2018 NOIP 提高组 复赛 day1_2018_67 组数据。 每组数据的第一行包含一个正整数 2018 NOIP 提高组 复赛 day1_复赛。接下来一行包含 2018 NOIP 提高组 复赛 day1_复赛 个由空格隔开的正整数 2018 NOIP 提高组 复赛 day1_提高组_45

输出格式:
输出文件共有 2018 NOIP 提高组 复赛 day1_2018_67 行,对于每组数据,输出一行一个正整数,表示所有与 2018 NOIP 提高组 复赛 day1_复赛_48 等价的货币系统 2018 NOIP 提高组 复赛 day1_复赛_60 中,最小的 2018 NOIP 提高组 复赛 day1_提高组_65

输入输出样例
输入样例#1:
2
4
3 19 10 6
5
11 29 13 19 17
输出样例#1:
2
5

T2 分析

照例的2018 NOIP 提高组 复赛 day1_复赛_76简单题,普及组第2题到第3题的难度。

稍微估计一下分数区,纯暴力dfs应该可能只有15分,加上优化之类的奇奇怪怪的操作应该能到30分~40分。

当然如果意识到一个点:货币系统B一定是货币系统A的子集,也就是所有钱币的数量都来自于A集合,那么就算dfs应该也可以到达80分。

这里有一点,其实前50分的数据范围就是在提醒你是这件事了,小数据却是非常大的2018 NOIP 提高组 复赛 day1_#include_77,大数据2018 NOIP 提高组 复赛 day1_#include_77却变小 : 2018 NOIP 提高组 复赛 day1_提高组_79

显然n小的数据通过放大2018 NOIP 提高组 复赛 day1_#include_77来限制分数,因为你没有发现这道题需要你去发现的点,而发现了这个点的人一定可以通过前50分的数据,所以后50分就不再关注2018 NOIP 提高组 复赛 day1_#include_77,当然这就是自己想想的…不要把这个当成判断题目做法的依据,这只能作为你让自己相信你的做法没有问题的一个非常小的理由。

如果发现了上面那个点,那就比较简单了,既然B一定是A的子集,那么只要去掉A中能被其他钱币组合出的钱币就可以了,也就是简化A,比如A中存在2 3 5三种钱币,显然5可以由2 + 3组成,那就不需要5了。

所以就是个简单的完全背包,标记一下哪些钱币能出现就可以了。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;

int f[25500],a[1500];

int main()
{
int pp;scanf("%d",&pp);
while (pp--){
int n;scanf("%d",&n);
int M = 0;
for (int i = 0 ; i < n ; ++i) scanf("%d",&a[i]),M = max(M,a[i]);

memset(f,0,sizeof(int) * (M + 5));
f[0] = 1;
for (int i = 0 ; i < n ; ++i)
for (int j = a[i] ; j <= M ; ++j)
f[j] += f[j - a[i]];

int ans = n;
for (int i = 0 ; i < n ; ++i)
if (f[a[i]] > 1)
--ans;
printf("%d\n",ans);
}
return 0;
}

T3 赛道修建

​题目点击→洛谷 P5021 赛道修建​​​ 题目描述
C 城将要举办一系列的赛车比赛。在比赛前,需要在城内修建 2018 NOIP 提高组 复赛 day1_提高组_65

C 城一共有 2018 NOIP 提高组 复赛 day1_复赛 个路口,这些路口编号为 2018 NOIP 提高组 复赛 day1_复赛_84,有 2018 NOIP 提高组 复赛 day1_复赛_85 条适合于修建赛道的双向通行的道路,每条道路连接着两个路口。其中,第 2018 NOIP 提高组 复赛 day1_#include_02 条道路连接的两个路口编号为 2018 NOIP 提高组 复赛 day1_#include_772018 NOIP 提高组 复赛 day1_复赛_88 ,该道路的长度为 2018 NOIP 提高组 复赛 day1_2018_89 。借助这 2018 NOIP 提高组 复赛 day1_复赛_85

一条赛道是一组互不相同的道路 2018 NOIP 提高组 复赛 day1_提高组_91 ,满足可以从某个路口出发,依次经过 道路 2018 NOIP 提高组 复赛 day1_提高组_91(每条道路经过一次,不允许调头)到达另一个路口。一条赛道的长度等于经过的各道路的长度之和。为保证安全,要求每条道路至多被一条赛道经过。

目前赛道修建的方案尚未确定。你的任务是设计一种赛道修建的方案,使得修建的 2018 NOIP 提高组 复赛 day1_提高组_65 条赛道中长度最小的赛道长度最大(即 2018 NOIP 提高组 复赛 day1_提高组_65

输入输出格式
输入格式:
输入文件第一行包含两个由空格分隔的正整数 2018 NOIP 提高组 复赛 day1_2018_95

接下来 2018 NOIP 提高组 复赛 day1_复赛_85 行,第 2018 NOIP 提高组 复赛 day1_#include_02 行包含三个正整数 2018 NOIP 提高组 复赛 day1_提高组_98 ,表示第 2018 NOIP 提高组 复赛 day1_#include_02 条适合于修建赛道的道路连接的两个路口编号及道路长度。保证任意两个路口均可通过这 2018 NOIP 提高组 复赛 day1_复赛_85

输出格式:
输出共一行,包含一个整数,表示长度最小的赛道长度的最大值。

输入输出样例
输入样例#1:
7 1
1 2 10
1 3 5
2 4 9
2 5 8
3 6 6
3 7 7
输出样例#1:
31
输入样例#2:
9 3
1 2 6
2 3 3
3 4 5
4 5 10
6 2 4
7 2 9
8 4 7
9 4 4
输出样例#2:
15

T3 分析

最后一题,需要一点思维性的东西。乍一眼一看就猜得出来肯定可以二分答案,那么问题就是check()怎么写了。

首先分析一下数据范围。

可以发现最显眼的一个条件就是 2018 NOIP 提高组 复赛 day1_#include_101

1、 2018 NOIP 提高组 复赛 day1_#include_101

解法:把所有边权记录下来,这种情况等价于将序列分割成 2018 NOIP 提高组 复赛 day1_提高组_65 段,使 2018 NOIP 提高组 复赛 day1_提高组_65

那么二分 2018 NOIP 提高组 复赛 day1_提高组_65 段区间和的最小值,然后 2018 NOIP 提高组 复赛 day1_noip_106 贪心扫一遍,时间复杂度 2018 NOIP 提高组 复赛 day1_#include_107

2、2018 NOIP 提高组 复赛 day1_2018_108 的情况
即这棵树的的深度为2,把所有边权记录下来,从大到小排序。设边权为 2018 NOIP 提高组 复赛 day1_#include_109,答案即为 2018 NOIP 提高组 复赛 day1_#include_110的最小值,时间复杂度 2018 NOIP 提高组 复赛 day1_#include_107

3、 2018 NOIP 提高组 复赛 day1_2018_112 的情况(树的直径)
解法:取一条最长链,即为树的直径问题,记录一下最大值和次大值,每次把最大 值传到它的父亲,时间复杂度 2018 NOIP 提高组 复赛 day1_noip_106

以上的情况全部考虑到的话就可以获得55分及以上了,那么这是找规律和暴力做的情况。
然后是第4种情况
4、分支不超过3 (最多是二叉树)
这种情况下对于某个长度要求 2018 NOIP 提高组 复赛 day1_#include_114 只有两种情况,对于任意一个点2018 NOIP 提高组 复赛 day1_noip_115的两个儿子有以下情况

  • 2018 NOIP 提高组 复赛 day1_#include_116
  • 2018 NOIP 提高组 复赛 day1_noip_117

也就是说
要么 2018 NOIP 提高组 复赛 day1_noip_115 的某个儿子节点能够形成的最长路已经满足需求长度2018 NOIP 提高组 复赛 day1_#include_114了,那么这条路就可以自成一条路,答案+1。
要么这个点的两个儿子节点能够形成的最长路都不满足需求长度2018 NOIP 提高组 复赛 day1_#include_114,但是如果把两个儿子节点的路通过节点 2018 NOIP 提高组 复赛 day1_noip_115

从这里我们可以发现一个问题,对于分支不超过3的节点是这样的,那么如果超过3呢?
显然我们依旧可以用 2018 NOIP 提高组 复赛 day1_noip_115 的儿子节点中任意两个点 2018 NOIP 提高组 复赛 day1_复赛_123 满足 2018 NOIP 提高组 复赛 day1_2018_124 拼出满足条件的路。
那么要拼出更多的路,显然就是贪心了。
只要尽量去拼出刚好满足条件的路就可以了。也就是对于任意一条边,选择另一条长度最小,但能够和这条边拼出满足条件的路,即对于点x,选择2018 NOIP 提高组 复赛 day1_提高组_125
(当然对于本身儿子节点的路径已经满足要求的点不需要拼接其他的路,即2018 NOIP 提高组 复赛 day1_提高组_126

那么现在只剩下一个问题,那就是如何考虑对 2018 NOIP 提高组 复赛 day1_noip_115 的父亲 2018 NOIP 提高组 复赛 day1_复赛_128 来说 2018 NOIP 提高组 复赛 day1_noip_115 的最长路。

这里我们可以发现,首先能够满足条件的边一定已经被选走,跟之后的操作已经无关了。

那么对于 2018 NOIP 提高组 复赛 day1_noip_115 的儿子节点里,剩下的就只有本身最长路径不满足k,并且还不能跟其他的兄弟路径拼出满足条件的路。

可以发现这些边,虽然通过2018 NOIP 提高组 复赛 day1_noip_115并不能拼出满足条件的路,但是更往上,到他们祖先节点,就有可能满足条件了。

但是我们也可以发现,因为所有需要到达2018 NOIP 提高组 复赛 day1_noip_115连向儿子节点的路,都需要经过2018 NOIP 提高组 复赛 day1_提高组_133,但是一条边只能使用一次,所以 2018 NOIP 提高组 复赛 day1_noip_115 连向儿子的路中,只能选择一条使用,加上2018 NOIP 提高组 复赛 day1_提高组_133从而形成对于2018 NOIP 提高组 复赛 day1_复赛_128 来说 2018 NOIP 提高组 复赛 day1_noip_115 的最长路。

2018 NOIP 提高组 复赛 day1_2018_138

那么就是代码的事情了,我们二分修建的2018 NOIP 提高组 复赛 day1_提高组_65条路中最短的长度2018 NOIP 提高组 复赛 day1_#include_114,然后用上面的方法check能否生成m条或以上的路满足长度≥k。这里为了方便组合两条边,我们可以使用multiset,时间复杂度 2018 NOIP 提高组 复赛 day1_复赛_141
当然这里存在一个小问题,第8组数据,当2018 NOIP 提高组 复赛 day1_#include_142时,如果二分的边界2018 NOIP 提高组 复赛 day1_提高组_143用极大值2018 NOIP 提高组 复赛 day1_复赛_144 的话就会超时,因为每次check的复杂度都是2018 NOIP 提高组 复赛 day1_提高组_145,而二分次数是log(500000000) 所以会超时。
如果没有注意到这一点,嫌麻烦直接写了个极大值边界,就只能95分

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <set>
#include <vector>
using namespace std;

int head[50100],to[100100],nex[100100],val[100100];
int n,waysnumber,m,edges;

multiset <int> :: iterator it;

int dfs(int now,int father,int x){
multiset <int> son;
for (int v = head[now]; v ; v = nex[v])
if (to[v] != father){
int tmpways = dfs(to[v],now,x) + val[v];
if (tmpways >= x) ++waysnumber;
else son.insert(tmpways);
}
//对当前点now的所有子路径进行组合,用刚好能满足≥x的最小的边组合
//并返回所有儿子中最长的无法被组合成满足条件路的点
int maxwaylen = 0;
while (son.size() > 1){
it = son.lower_bound(x - *son.begin());
if (it == son.begin()) it++; //若最短的边≥x的时候,则用最短的两条边组合
if (it == son.end()){ //当前最短的边没办法找到够长的边组合出满足条件的路
//挑一条最长的子边返回
maxwaylen = max(maxwaylen,*son.begin());
son.erase(son.begin());
}
else{
//成功组合出了满足条件的边
++waysnumber;
son.erase(it);
son.erase(son.begin());
}
}
if (son.size() == 1) maxwaylen = max(maxwaylen,*son.begin());
return maxwaylen;
}

bool check(int x){
waysnumber = 0;
dfs(1,0,x);
if (waysnumber >= m) return true;
else return false;
}

void addedge(int u,int v,int c){
nex[++edges] = head[u], head[u] = edges, to[edges] = v, val[edges] = c;
}

int main()
{
edges = 0;
scanf("%d%d",&n,&m);
long long all = 0;
for (int i = 1 ; i < n ; ++i){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
addedge(a,b,c);
addedge(b,a,c);
all += c;
}
int l = 0 , r = all / m,mid,ans; //这里存在一个坑,对于ai = 1的图,如果这里的 r 使用极大值500000000,就会超时
while (l <= r){
mid = (l + r) >> 1;
if (check(mid)) ans = mid,l = mid + 1;
else r = mid - 1;
}
printf("%d\n",ans);
return 0;
}