$NOIP\ 2018\ Day1$ 模拟考试 题解报告
\(NOIP\ 2018\ Day1\) 模拟考试 题解报告
得分情况
\(T1\ 100\ Pts\)
\(T2\ 100\ Pts\)
\(T3\ 55\ Pts\)
总分: \(255\ Pts\)
考试过程
五分钟过 \(T1\)
二十分钟过 \(T2\)
一个小时写完 \(T3\) 部分分
弃 再之后没有什么进展
题解
感觉像是贪心专场
众所周知 贪心不需要证明
\(T1\) 铺设道路
贪心
前面比后面的大 答案累加两者的差
/*
Time: 6.5
Worker: Blank_space
Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
inline void File() {
freopen("road.in", "r", stdin);
freopen("road.out", "w", stdout);
}
/*----------------------------------------文件*/
int n, d[B], ans;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
/*----------------------------------------函数*/
signed main() {
File();
n = read(); d[n + 1] = 0;
for(int i = 1; i <= n; i++) d[i] = read();
for(int i = 1; i <= n; i++) if(d[i] > d[i + 1]) ans += d[i] - d[i + 1];
printf("%lld", ans);
return 0;
}
/*
贪心
五分钟 过大样例
*/
\(T2\) 货币系统
完全背包裸题
注意一下容量取最大值就能过
/*
Time: 6.5
Worker: Blank_space
Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define emm(x) memset(x, 0, sizeof x)
/*--------------------------------------头文件*/
const int B = 1e5 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
inline void File() {
freopen("money.in", "r", stdin);
freopen("money.out", "w", stdout);
}
/*----------------------------------------文件*/
int T, n, a[110], m, ans;
bool f[B], vis[110];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
void work() {
n = read(); ans = n; emm(f); emm(vis); f[0] = 1; m = 0;
for(int i = 1; i <= n; i++) a[i] = read(), m = Max(m, a[i]), vis[i] = 1;
std::sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; i++)
{
for(int j = a[i]; j <= m; j++) f[j] |= f[j - a[i]];
for(int j = i + 1; j <= n; j++) if(vis[j] && f[a[j]]) vis[j] = 0, ans--;
}
printf("%d\n", ans);
}
/*----------------------------------------函数*/
int main() {
File();
T = read(); while(T--) work();
return 0;
}
/*
2
4
3 19 10 6
5
11 29 13 19 17
有点类似背包 完全背包
n 比较小
缀和求容量
贪一下 小的放在前面 跑个背包
小样例过了 大样例过了 但是跑的有点慢 跑了五秒多
只需要考虑能否凑出 容量从前缀和改为最大值 过了
20分钟 过大样例
*/
\(T3\) 赛道修建
\(55\) 的部分分是送的 略过了
代码
/*
Time: 6.5
Worker: Blank_space
Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 5e4 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
inline void File() {
freopen("track.in", "r", stdin);
freopen("track.out", "w", stdout);
}
/*----------------------------------------文件*/
int n, m, du[A], f[A], g[A], ans, maxdu, sum, cnt, _d, dp[A];
struct node {int u, v, w;} a[A];
struct edge {int v, w, nxt;} e[A << 1];
int head[A], ecnt;
std::pair <int, int> d[A];
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
void add_edge(int u, int v, int w) {e[++ecnt] = (edge){v, w, head[u]}; head[u] = ecnt;}
void dfs1(int u, int pre) {
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v, w = e[i].w;
if(v == pre) continue;
dfs1(v, u);
if(f[v] + w > f[u]) g[u] = f[u], f[u] = f[v] + w;
else if(f[v] + w > g[u]) g[u] = f[v] + w;
}
ans = Max(ans, f[u] + g[u]);
}
void work1() {
dfs1(1, 0);
printf("%lld", ans);
}
void dfs2(int u, int pre, int _s, int x) {
if(_s >= x) cnt++, _s = 0;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v, w = e[i].w;
if(v == pre) continue;
dfs2(v, u, _s + w, x);
}
}
bool check2(int x) {
cnt = 0; dfs2(1, 0, 0, x);
return cnt >= m;
}
void work2() {
int l = 0, r = sum;
while(l <= r)
{
int mid = l + r >> 1;
if(check2(mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
printf("%lld", ans);
}
bool cmp(node x, node y) {return x.w > y.w;}
bool check3(int x) {
cnt = 0;
for(int i = 1, j = n - 1; i <= j; i++)
{
if(a[i].w >= x) cnt++;
else
{
if(i == j) break;
while(a[i].w + a[j].w < x && i < j - 1) j--;
if(a[i].w + a[j].w >= x) cnt++, j--;
else break;
}
}
return cnt >= m;
}
void work3() {
std::sort(a + 1, a + n, cmp);
int l = 0, r = sum;
while(l <= r)
{
int mid = l + r >> 1;
if(check3(mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
printf("%lld", ans);
}
/*----------------------------------------函数*/
signed main() {
File();
n = read(); m = read();
for(int i = 1; i < n; i++)
{
int x = read(), y = read(), z = read();
a[i] = (node){x, y, z}; sum += z;
du[x]++; du[y]++; maxdu = Max(maxdu, Max(du[x], du[y]));
add_edge(x, y, z); add_edge(y, x, z);
}
if(m == 1) work1();
else if(maxdu == 2) work2();
else if(maxdu == n - 1) work3();
else puts("18109");
return 0;
}
/*
最小最大 二分答案 先扔了 看数据
m = 1 树的直径 20
7 1
1 2 10
1 3 5
2 4 9
2 5 8
3 6 6
3 7 7
链 20
每条赛道一定是连续的一段 二分答案 进行check
二分一个最小值
8 x
1 2 7
3 4 2
2 3 3
7 8 1
6 7 4
5 6 5
4 5 6
过小样例 应该差不多
菊花图 15
一条赛道最多只能有两条边
二分答案 进行check
按边权排序 双指针 贪
10 x
1 3 6
1 10 1
1 2 8
1 4 9
1 9 7
1 5 3
1 6 4
1 7 2
1 8 8
过小样例 跑路
大概能过前 11 个点
二叉树 25
12 - 16
没思路 感觉这个已经接近正解了
尝试开正解
一棵树 最多只能有一条边连出去
贪一下 能在子树内连的尽量在子树内连
尝试维护唯一一条连出去的边
一定是除却已经连接的边的最长的边
父节点中 用所有儿子中连出的这条边进行相互匹配
排序在匹配复杂度爆炸
写不出来 弃了
卒
*/
正解
看题很自然的想到二分答案 而且发现我上面那个贪心思路是对的... 想到了排序内部匹配 没想到二分查找 假掉了
二分一个最小值 进行check
直接递归到最底层 回溯的时候在每一棵子树内进行自匹配 对于一条没有被选过的边 通过排序 + 二分查找 找一条最短的能够满足条件的边进行匹配 并把这条边标记 对于每一个节点 维护从这棵子树内出去(还没有被匹配)的最长的链 再回过头来看上面的自匹配 把当前节点的以儿子节点为根的子树内出来的最长链加上那一条边 再存一个数组 排序后在这个数组中进行操作
代码
/*
Time: 6.5
Worker: Blank_space
Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------头文件*/
const int A = 5e4 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
inline void File() {
freopen("track.in", "r", stdin);
freopen("track.out", "w", stdout);
}
/*----------------------------------------文件*/
int n, m, du[A], f[A], g[A], ans, maxdu, sum, cnt, q[A], K, dp[A], vis[A];
struct node {int u, v, w;} a[A];
struct edge {int v, w, nxt;} e[A << 1];
int head[A], ecnt;
/*------------------------------------变量定义*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快读*/
void add_edge(int u, int v, int w) {e[++ecnt] = (edge){v, w, head[u]}; head[u] = ecnt;}
void dfs1(int u, int pre) {
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v, w = e[i].w;
if(v == pre) continue;
dfs1(v, u);
if(f[v] + w > f[u]) g[u] = f[u], f[u] = f[v] + w;
else if(f[v] + w > g[u]) g[u] = f[v] + w;
}
ans = Max(ans, f[u] + g[u]);
}
void work1() {
dfs1(1, 0);
printf("%lld", ans);
}
void dfs2(int u, int pre, int _s, int x) {
if(_s >= x) cnt++, _s = 0;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v, w = e[i].w;
if(v == pre) continue;
dfs2(v, u, _s + w, x);
}
}
bool check2(int x) {
cnt = 0; dfs2(1, 0, 0, x);
return cnt >= m;
}
void work2() {
int l = 0, r = sum;
while(l <= r)
{
int mid = l + r >> 1;
if(check2(mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
printf("%lld", ans);
}
bool cmp(node x, node y) {return x.w > y.w;}
bool check3(int x) {
cnt = 0;
for(int i = 1, j = n - 1; i <= j; i++)
{
if(a[i].w >= x) cnt++;
else
{
if(i == j) break;
while(a[i].w + a[j].w < x && i < j - 1) j--;
if(a[i].w + a[j].w >= x) cnt++, j--;
else break;
}
}
return cnt >= m;
}
void work3() {
std::sort(a + 1, a + n, cmp);
int l = 0, r = sum;
while(l <= r)
{
int mid = l + r >> 1;
if(check3(mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
printf("%lld", ans);
}
void dfs(int u, int pre, int x) {
for(int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v) if(v != pre) dfs(v, u, x);
K = 0; dp[u] = 0;
for(int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v) if(v != pre) q[++K] = dp[v] + e[i].w;
std::sort(q + 1, q + 1 + K);
for(int i = K; i && q[i] >= x; i--) cnt++, K--;
for(int i = 1; i <= K; i++) if(vis[i] != u)
{
int L = i + 1, R = K, pos = K + 1;
while(L <= R)
{
int Mid = L + R >> 1;
if(q[i] + q[Mid] >= x) pos = Mid, R = Mid - 1;
else L = Mid + 1;
}
while(vis[pos] == u && pos <= K) pos++;
if(pos <= K) vis[i] = vis[pos] = u, cnt++;
}
for(int i = K; i; i--) if(vis[i] != u) {dp[u] = q[i]; break;}
}
bool check(int x) {
memset(vis, 0, sizeof vis);
cnt = 0; dfs(1, 0, x);
return cnt >= m;
}
void work() {
int l = 0, r = sum;
while(l <= r)
{
int mid = l + r >> 1;
if(check(mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
printf("%lld", ans);
}
/*----------------------------------------函数*/
signed main() {
File();
n = read(); m = read();
for(int i = 1; i < n; i++)
{
int x = read(), y = read(), z = read();
a[i] = (node){x, y, z}; sum += z;
du[x]++; du[y]++; maxdu = Max(maxdu, Max(du[x], du[y]));
add_edge(x, y, z); add_edge(y, x, z);
}
if(m == 1) work1();
else if(maxdu == 2) work2();
else if(maxdu == n - 1) work3();
else work();
return 0;
}
/*
最小最大 二分答案 先扔了 看数据
m = 1 树的直径 20
7 1
1 2 10
1 3 5
2 4 9
2 5 8
3 6 6
3 7 7
链 20
每条赛道一定是连续的一段 二分答案 进行check
二分一个最小值
8 x
1 2 7
3 4 2
2 3 3
7 8 1
6 7 4
5 6 5
4 5 6
过小样例 应该差不多
菊花图 15
一条赛道最多只能有两条边
二分答案 进行check
按边权排序 双指针 贪
10 x
1 3 6
1 10 1
1 2 8
1 4 9
1 9 7
1 5 3
1 6 4
1 7 2
1 8 8
过小样例 跑路
大概能过前 11 个点
二叉树 25
12 - 16
没思路 感觉这个已经接近正解了
尝试直接开正解
一棵树 最多只能有一条边连出去
贪一下 能在子树内连的尽量在子树内连
尝试维护唯一一条连出去的边
一定是除却已经连接的边的最长的边
父节点中 用所有儿子中连出的这条边进行相互匹配
排序在匹配复杂度爆炸
写不出来 弃了
卒
*/