APIO2016 题解
来源:互联网 发布:sql把重复数据删除 编辑:程序博客网 时间:2024/06/05 06:37
今年的题比去年的不知道高到哪里去了。。。
Orz 蛤省Cu观光团
Orz cstdio
Orz 韩国出题人
A. 划艇
先吐槽:这题不是叫划艇么,怎么就赛艇了?
BZOJ4584:[Apio2016]赛艇
题目
【问题描述】
在首尔城中,汉江横贯东西。在汉江的北岸,从西向东星星点点地分布着
值得注意的是,编号为
【任务描述】
输入所有学校的
【输入格式】
第一行包括一个整数
接下来
【输出格式】
输出一行,一个整数,表示所有可能的派出划艇的方案数除以
【样例输入】
21 22 3
【样例输出】
7
【样例说明】
在只有一所学校派出划艇的情况下有4种方案,两所学校都派出划艇的情况下有3种方案,所以答案为7。
【子任务】
子任务 1(9 分):
子任务 2(22 分):
子任务 3(27 分):
子任务 4(42 分):
9分算法
子任务1中一定满足
设
最后输出
时间复杂度
100分算法
中间的子任务没什么意思。。。估计是照顾乱搞党?不管了直接说标算。
这种输入很少,但数字范围很大的问题,往往可以离散化处理。为了方便处理
比如说,输入中的所有数据排序去重后,得到一个序列map
都是可以的。
然后,所有的值就都不超过
然后考虑怎么转移。当前要计算
· 之前取的数不在区间
· 之前取的数在区间
先看一个很简单的问题:在区间
小学数学题吧,答案是
那如果取3个数
为了方便,我们把
分情况讨论一下。
· 一个区间都不选,那么有
· 选一个区间,那么有
· 选两个区间,那么有
· 选三个区间,那么有
因此总共有
还是用组合形式表述吧。取法总数为
是不是似曾相识?有没有在高中课本上见过?它明显是等于
因此,上述问题稍加扩展,就是:
给你x个相同的区间(
好了,这个重要结论得到之后,我们回到问题。不是要求
然后在
只要稍作代换,不难发现它的值等于
似乎很完美?但一看这式子就知道复杂度是
不难发现,凡是出现二维前缀和的地方,都是矩形左上角一块的所有数之和,那么再维护一下二维前缀和就好了,时间复杂度降至
还有一个问题,就是组合数的计算。显然直接按照定义是不行的,最好根据杨辉三角什么的先预处理出来组合数表,当然也可以利用递推式
当然还有别的优化(据说可以FFT?蒟蒻表示并看不出来),不过到这里已经足够了,时间复杂度
#include <cstdio>#include <set>#include <algorithm>using namespace std;#define Menci 1000000007//Let's Orz Menci Togetherint n;int inv[501];int a[501], b[501];set<int> nums;int cnt, get[1002];int l[1002];int ans[501][1002];int main(){ scanf("%d", &n); inv[1] = 1; for (int i = 2; i <= 500; i++) inv[i] = (long long)(Menci - Menci / i) * inv[Menci % i] % Menci; for (int i = 1; i <= n; i++) { scanf("%d%d", &a[i], &b[i]); if (!nums.count(a[i])) nums.insert(a[i]); if (!nums.count(b[i] + 1)) nums.insert(b[i] + 1); } for (set<int>::iterator it = nums.begin(); it != nums.end(); it++) get[cnt++] = *it; for (int i = 1; i < cnt; i++) l[i] = get[i] - get[i - 1]; for (int i = 1; i <= n; i++) { a[i] = upper_bound(get, get + cnt, a[i]) - get; b[i] = upper_bound(get, get + cnt, b[i]) - get; } for (int i = 0; i < cnt; i++) ans[0][i] = 1; for (int i = 1; i <= n; i++) { ans[i][0] = 1; for (int j = a[i]; j <= b[i]; j++) { ans[i][j] = ((long long)ans[i - 1][j - 1] * l[j]) % Menci; int now = 1, c = l[j] - 1; for (int k = i - 1; k; k--) { if (a[k] <= j && j <= b[k]) { now++; c = (long long)c * (l[j] + now - 2) % Menci * inv[now] % Menci; if (!c) break; ans[i][j] = (ans[i][j] + (long long)ans[k - 1][j - 1] * c) % Menci; } } } for (int j = 1; j < cnt; j++) ans[i][j] = ((long long)ans[i][j] + ans[i - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1] + Menci) % Menci; } printf("%d\n", (ans[n][cnt - 1] - 1 + Menci) % Menci); return 0;}
B. 烟花表演
题目
【问题描述】
烟花表演是最引人注目的节日活动之一。在表演中,所有的烟花必须同时爆炸。为了确保安全,烟花被安置在远离开关的位置上,通过一些导火索与开关相连。导火索的连接方式形成一棵树,烟花是树叶,如[图1]所示。火花从开关出发,沿导火索移动。每当火花抵达一个分叉点时,它会扩散到与之相连的所有导火索,继续燃烧。导火索燃烧的速度是一个固定常数。[图1]展示了六枚烟花{
Hyunmin为烟花表演设计了导火索的连线布局。不幸的是,在他设计的布局中,烟花不一定同时爆炸。我们希望修改一些导火索的长度,让所有烟花在同一时刻爆炸。例如,为了让[图1]中的所有烟花在时刻
修改导火索长度的代价等于修改前后长度之差的绝对值。例如,将[图1]中布局修改为[图2]左边布局的总代价为
导火索的长度可以被减为
给定一个导火索的连线布局,你需要编写一个程序,去调整导火索长度,让所有的烟花在同一时刻爆炸,并使得代价最小。
【输入格式】
所有的输入均为正整数。令
输入格式如下:
其中
【输出格式】
输出调整导火索长度,让所有烟花同时爆炸,所需要的最小代价。
【样例输入】
4 61 52 52 83 33 23 32 94 44 3
【样例输出】
5
【子任务】
子任务 1(7 分):
子任务 2(19 分):
子任务 3(29 分):
子任务 4(45 分):
cstdio神犇的PPT
点击这里查看
7分算法
只有一个分叉点的话,显然改动分叉点到根结点的路没有卵用,问题转化为把一坨东西改成一样大所需的最小代价,求一下中位数就行了。
时间复杂度
26分算法
数据规模那么小,不妨这样表示状态:
当然预处理要把不合法的东西搞得很大很大。
时间复杂度
结合算法一获得26分。
100分算法
这个建模真是巧妙,最好去看上面的课件,下面的内容纯属口胡。
这个费用啊,明显是和绝对值函数有点关系,最后肯定是一个分段函数。设函数
F(0) 等于树上所有边权和。- 当
x 足够大时,F′(x) 的值为一常数,它的值等于根结点的儿子个数。
这样,只要知道所有拐点(当然这里强制规定所有拐点导致斜率增加1), 这个函数就可以画出来,再求最值就是小学问题了。
问题就是这些拐点怎么求。当然,根据问题的性质,上述性质对任意一棵子树都是满足的,这就启示我们要进行合并。
合并之前,因为子树要连上来,所以要增加一条边,显然当
合并就是很简单的并在一起,注意可重。显然对于删点、加点、合并的操作,可并堆是坠吼的数据结构,打一个就好。
时间复杂度
#include <cstdio>#include <cctype>#include <algorithm>#include <vector>using namespace std;#define MAXN 300010inline int readint(){ int x = 0; char c = getchar(); while (isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); } return x;}struct node{ long long data, dis; node *son[2]; node(long long d = 0) { data = d; dis = 0; son[0] = son[1] = 0; }};node* merge(node* x, node* y){ if (!x) return y; if (!y) return x; if (x->data < y->data) swap(x, y); x->son[1] = merge(x->son[1], y); int d1 = (x->son[0] ? x->son[0]->dis : 0); int d2 = (x->son[1] ? x->son[1]->dis : 0); if (d1 < d2) swap(x->son[0], x->son[1]); if (x->son[1]) x->dis = x->son[1]->dis + 1; else x->dis = 0; return x;}node *root[MAXN];int n, m;long long p[MAXN], c[MAXN];long long sum[MAXN], son[MAXN];vector<long long> point;int main(){ n = readint(); m = readint(); for (int i = 2; i <= n + m; i++) { p[i] = readint(); c[i] = readint(); } for (int i = n + m; i >= 2; i--) { sum[p[i]] += sum[i] + c[i]; son[p[i]] ++; } for (int i = n + m; i >= 2; i--) { if (!root[i]) root[i] = merge(new node(c[i]), new node(c[i])); else { long long l, r; for (int j = 0; j <= son[i]; j++) { if (j == son[i] - 1) r = root[i]->data; if (j == son[i]) l = root[i]->data; root[i] = merge(root[i]->son[0], root[i]->son[1]); } root[i] = merge(root[i], new node(l + c[i])); root[i] = merge(root[i], new node(r + c[i])); } root[p[i]] = merge(root[p[i]], root[i]); } while (root[1]) { point.push_back(root[1]->data); root[1] = merge(root[1]->son[0], root[1]->son[1]); } int sz = (int)point.size(); for (int i = 0; i < sz - i - 1; i++) swap(point[i], point[sz - i - 1]); long long now = sum[1], k = son[1] - sz; for (int i = 0; k; i++) { now += k * (i ? point[i] - point[i - 1] : point[i]); k++; } printf("%lld", now); return 0;}
C. 最大差分
题目
【问题描述】
有
你的程序不能直接读入这个整数序列,但是你可以通过给定的函数来查询该序列的信息。关于查询函数的细节,请根据你所使用的语言,参考下面的实现细节部分。
你需要实现一个函数,该函数返回
(实现细节及样例略,可参考UOJ#206)
官方题解
点击这里下载
30.38分算法
先询问MinMax(0, inf, &mn, &mx)
,得到整个序列的最大最小值,然后分别替换掉调用时的上下界,即询问MinMax(mn + 1, mx - 1, &mn, &mx)
,依次类推,每次确定下来2个元素,直到确定下来所有元素。
然后就随便搞了。显然在子任务1中,询问次数不超过
100分算法
首先考虑一个问题,答案的范围。
首先调用MinMax(0, inf, &mn, &mx)
,得到最大最小值,那么答案的上界就是
因此,把整数区间
为什么这样是对的呢?首先,根据上面的分析,最大差分不可能在同一块中取得,而在不同块中的差分,必然只能是快内的最大最小值才有可能接触到别的块中的元素,所以这样是没问题的。
总询问代价怎么算呢?首先,两个极值各询问了
我觉得可能在某些数据可以有更优解的。。。
#include "gap.h"#define MAXN 1000000000000000000LLlong long a[200010];long long findGap(int T, int N){ if (T == 1) { long long nowmin, nowmax; MinMax(0, MAXN, &nowmin, &nowmax); a[1] = nowmin, a[N] = nowmax; for (int i = 2; i <= N - i + 1; i++) { MinMax(a[i - 1] + 1, a[N - i + 2] - 1, &nowmin, &nowmax); a[i] = nowmin, a[N - i + 1] = nowmax; } long long ans = 0; for (int i = 1; i < N; i++) if (a[i + 1] - a[i] > ans) ans = a[i + 1] - a[i]; return ans; } else { long long nowmin, nowmax; MinMax(0, MAXN, &nowmin, &nowmax); long long block = (nowmax - nowmin) / (N - 1); if ((nowmax - nowmin) % (N - 1) != 0) block++; int cnt = 1; a[0] = nowmin; for (int i = 0; i < N; i++) { long long l = nowmin + block * i + 1, r = nowmin + block * (i + 1); if (r == nowmax) r--; if (l >= nowmax) break; long long lsmin, lsmax; MinMax(l, r, &lsmin, &lsmax); if (lsmin > -1) { a[cnt++] = lsmin; a[cnt++] = lsmax; } } a[cnt++] = nowmax; long long ans = 0; for (int i = 0; i < cnt - 1; i++) if (a[i + 1] - a[i] > ans) ans = a[i + 1] - a[i]; return ans; }}
这题还是不错的。。。至少让我看见自己有多么智障。。。 (劳资干吗不去?去了怎么还没个Ag啊。。。艹)
- APIO2016 题解
- bzoj3917 && apio2016练习赛T2 题解
- APIO2016游记
- APIO2016游记
- APIO2016总结
- Apio2016 游记
- APIO2016总结
- APIO2016滚粗记
- CTSC2016&&APIO2016爆零记
- ctsc&apio2016 总结
- 【bzoj4584】[Apio2016]赛艇 dp
- bzoj4584: [Apio2016]赛艇
- BZOJ4585 [Apio2016]烟火表演
- BZOJ4584 [Apio2016]赛艇
- 【APIO2016】字符串匹配
- BZOJ 4584 [Apio2016]赛艇
- [交互题] APIO2016 Gap
- BZOJ4584: [Apio2016]赛艇
- iOS开发 GET、POST请求方法:NSURLConnection篇
- crond命令
- 华为 hg8245c 超级密码
- 《三重门》作者的机灵与人物的笨拙
- 基于ENVI下的土地利用信息提取(三)
- APIO2016 题解
- Spring Mvc那点事---(17)Spring Mvc之数据绑定
- LPC1768 IAP升级
- iOS获取手势的tag
- 简单工厂VS工厂方法VS抽象工厂
- 十三.通过pagination对页面进行分页
- ArrayList简介
- 【造轮子系列】一个选择星期的工具——SweepSelect View
- 解决ExpandableListView的OnItemLongClickListener无法准确获取position的问题