THOI2016 题解

仔细确认了下,这比赛确实叫做THOI,上次似乎打错了。
感谢mzx神犇提供的帮助。

A. 成绩单(scorelist)

题目

【问题描述】
期末考试结束了,班主任 L 老师要将成绩单分发到每位同学手中。 L 老师共有 n 份成绩单,按照编号从1 n 的顺序叠放在桌子上,其中编号为i的成绩单分数为 wi

成绩单是按照批次发放的。发放成绩单时,L 老师会从当前的一叠成绩单中抽取连续的一段,让这些同学来领取自己的成绩单。当这批同学领取完毕后, L 老师再从剩余的成绩单中抽取连续的一段,供下一批同学领取。经过若干批次的领取后,成绩单将被全部发放到同学手中。

然而,分发成绩单是一件令人头痛的事情,一方面要照顾同学们的心理情绪,不能让分数相差太远的同学在同一批领取成绩单;另一方面要考虑时间成本,尽量减少领取成绩单的批次数。对于一个分发成绩单的方案,我们定义其代价为:

a×k+b×i=1k(maximini)2

其中, k 是方案中分发成绩单的批次数,对于第i批分发的成绩单, maxi 是最高分数, mini 是最低分数。 a,b 是给定的评估参数。

现在,请你帮助 L 老师找到代价最小的分发成绩单的方案,并将这个最小的代价告诉 L 老师。当然,分发成绩单的批次数 k 是由你决定的。

【输入格式】

第一行包含一个正整数n,表示成绩单的数量。

第二行包含两个非负整数 a,b ,表示给定的评估参数。

第三行包含 n 个正整数wi,表示第 i 张成绩单上的分数。

【输出格式】

仅一个正整数,表示最小的代价是多少。

【样例输入1】

10
5 1
1 2 6 10 8 6 6 4 2 2

【样例输出1】

24

【样例输入2】

9
10 3
5 8 9 5 8 6 10 5 1

【样例输出2】

56

【样例说明】

样例1的具体方案为:

第1批,分发[10,8],落差为 2 ,剩余成绩单为[1,2,6,6,6,4,2,2]
第2批,分发 [6,6,6,4] ,落差为 2 ,剩余成绩单为[1,2,2,2]
第3批,分发 [1,2,2,2] ,落差为 1 ,分发完毕。
总代价为3×5+(22+22+12)×1=24

样例2的具体方案为:

第1批,分发 [8,9] ,落差为 1 ,剩余成绩单为[5,5,8,6,10,5,1]
第2批,分发 [8] ,落差为 0 ,剩余成绩单为[5,5,6,10,5,1]
第3批,分发 [10] ,落差为 0 ,剩余成绩单为[5,5,6,5,1]
第4批,分发 [5,5,6,5] ,落差为 1 ,剩余成绩单为[1]
第5批,分发 [1] ,落差为 0 ,分发完毕。
总代价为5×10+(12+02+02+12+02)×3=56

【数据规模与约定】

对于全部数据,满足 0a,b200 1wi5000

单调序列:满足 w1w2wn w1w2wn

单峰序列:存在 1in ,满足 w1w2wi wiwi+1wn

20分算法

对于前面4个测试点,保证是单调序列,根据题目性质,每次分发的成绩单肯定是连续的一段。因此我们记 f[i] 表示分发完前 i 份成绩单所需的最小费用,则状态转移方程为

f[i]=min1j<i{f[j]+a+b×(w[i]w[j+1])2}

时间复杂度 O(n2) ,期望得分20分。

40分算法

对于5,6,7,8四个测试点,给出的序列是单峰的。直觉告诉我们,每次取出来的区间都要包含“峰”。因此先扫一遍把峰值找出来记为 m ,然后记f[i][j]表示峰值左面 i 个位置,右面j个位置形成的区间,分发所需的最小代价,那么状态转移方程大概是

f[i][j]=min0k<i,0l<j{f[k][l]+a+b×(max{w[mk],w[m+l]}min{w[mi+1],w[m+j1]})2}

时间复杂度 O(n4) ,结合20分算法期望得分40分。

60 70分算法

爆搜?状压DP?xjb搞搞说不定就70了。

55分算法

考虑区间DP。记 f[i][j] 为把区间 [i,j] 分发完毕所需的最小代价。很显然,这里的区间性质并不是很好,因此常见的区间合并做法明显是行不通的。

枚举最后一次合并时,成绩单中的最大值和最小值。这会导致一部分位置的成绩不允许在最后一次合并中出现,不放设它们从小到大构成了一个有序表 l[1k] 。既然不允许出现,那么它们必然要在之前消去,这就是一个个的子问题,要用到较短区间的DP结果快速出解。

首先把 l 分成一个个的连续段,对于每个连续段,设它的左边界和右边界分别为left,right,那么结果上要加 f[left][right] ,这样把所有的连续段都计算过之后,加上本次枚举到的最大最小值造成的费用,就是当前最优解,对于所有枚举到的最大最小值取最优解就行。

时间复杂度 O(n5) ,期望得分100分。但是,这个算法实际上是错误的。。。。但是随机数据反例很不好找。。。。最后只有55分。

85分算法

把前面几个算法拼起来就有85分了。好吧这是个道听途说的数据。

100分算法

还是55分算法那里,只要把对于每个连通块直接计算的贪心,换成考虑之前区间的一个线性DP,保证这里能取到最优解。

不过这东西的正确性证明已经超出我的思考能力了。。。而且复杂度、常数那一套理论我是不懂得咯。。。你萌快去抱mzx大爷大腿。。。

时间复杂度依然是 O(n5) (可惜我这傻X写了 O(n6) 的算法,虽然自己测试并不TLE),期望得分100分。

B.补退选(selection)

题目

【问题描述】
X 是 T 大的一名教师,每年他都会给学生开设《C++编程基础》课程。每个学期开课之前,学生首先需要选择本学期的课程。选课一共分为三个阶段:预选,正选,补退选;其中“补退选”阶段最为忙碌。

在补退选阶段,学生既可以选课,也可以退课。对于 X 老师来说,在补退选阶段可能发生选课和退课两种事件。

选课:一个姓名为 S 的学生选了这门课(姓名S将出现在学生名单中)。

退课:一个姓名为 S 的学生退了这门课(姓名S将从学生名单中移除)。

同时, X 老师对于有哪些学生选了这门课非常关心,所以他会不定时地查询学生名单。我们把查询也当作一种事件。

查询:给定一个前缀 T 和一个整数v,询问最早在哪个事件发生后,姓名以 T 为前缀的学生数量超过了v

X 老师听说你编程能力出色,所以想用这个问题考考你。你当然不会畏惧,所以勇敢地接下了这个任务。请帮助 X 老师完成所有的查询操作。

注意 1:学生的姓名可能相同,如果有 p 个姓名相同的学生都选了 X 老师的课,则它们的姓名将出现在 X 老师的名单上p次。

注意 2:只有已经选了课的学生才会退课,如果姓名为 S 的学生退课,则在他退课之前 X 老师的名单上一定有姓名S

注意 3:事件的编号从 1 开始。

【输入格式】

第一行包含一个正整数n,表示一共发生了 n 个事件。

接下来n行,每行描述一个事件;每行第一个正整数 k 表示事件类型:

如果k=1,表示选课事件,接下来一个字符串 S ,表示一个姓名为S的学生选了 X 老师的课;

如果 k=2 ,表示退课事件,接下来一个字符串 S ,表示一个姓名为S的学生退了 X 老师的课;

如果 k=3 ,表示查询事件,接下来一个字符串 T 以及三个非负整数a,b,c。请你通过给定的 a,b,c 来计算出 v=(a×|ANS|+b)%c ,其中 |ANS| 表示上次查询事件的答案的绝对值,如果当前是第1次查询,则 |ANS|=0 。 X 老师想知道最早在第几个事件之后,姓名以 T 为前缀的学生数量超过了v。如果任何时刻都没有超过该值,则答案为 1

注:输入中的所有字符串均只包含英文字母表中前10个小写字母。

【输出格式】

对于每个查询事件,输出一行表示该查询答案。

【样例输入1】

10
1 abbe
1 bab
3 ba 0 1 10
1 bab
3 ba 0 1 10
1 bab
3 ba 0 2 10
2 bab
3 ba 0 2 10
3 ba 0 3 10

【样例输出1】

-1
4
6
6
-1

【样例输入2】

10
1 bage
1 bab
3 ba 6 1 10
1 bab
3 ba 8 1 4
1 bab
3 ba 7 2 13
2 bab
3 ba 4 2 11
3 ba 1 2 17

【样例输出2】

2
2
6
-1
6

【样例说明】

对于样例1,事件的进程如下表所示:

【数据规模与约定】

20分算法

裸的暴力,每次询问暴力模拟之前的所有操作。

时间复杂度 O(n2|S|) ,期望得分20分。

50分算法

5到10这6个测试点,没有退课操作,也就是说任何前缀的学生数目单调不减,所以建立一棵可持久化的Trie树,二分答案,然后再Trie上统计就好。

时间复杂度 O(n|S|logn) ,期望得分40分,结合算法一50分。

100分算法(一)

有了退课操作之后,50分算法中依赖的单调性不存在了,但是我们可以在Trie树的节点上额外保存一个历史最大值,这样又满足了单调性,并且正确性不难证明。

时间复杂度 O(n|S|logn) ,期望得分100分。

100分算法(二)

可持久化不会写怎么办啊?

嗯,那就先把所有输入读进来,对所有见过的串建立Trie树。在Trie树的每个节点处,我们记录这样一个数组,数组的第 i 个元素表示以从根结点到这个结点构成的字符串为前缀的名字的数量第一次达到i的时刻。

不对啊,这数组得开多大啊,空间复杂度到 n2 了吧。。。

换个角度。每次操作显然只对最多 |S| 个结点有影响,那么总共更新这数组的次数不超过 n|S| ,所以大部分空间浪费了。事实上,绝大多数的结点处的数组,后面很大一串都是空的。

所以只要开成动态数组就行了。询问显然是直接查询。

时间复杂度为 O(n|S|) ,期望得分100分。

星露谷物语(stardew)

提答题拿不到数据能玩?按照记忆瞎扯两句好了。

题目大意

给你很多有向线段,你在坐标平面内任意走,要求路径必须覆盖所有线段(当然必须是同向经过),求长度的最小值。

一个合适的画图工具对解决这个题目有很大的帮助。

测试点1

很显然,这一堆线段是首尾相连的,显然按照它的指示绕一圈就好,根本不浪费。

测试点2

似乎没什么性质了?拿衣服。

用画图的方法或者某些厉害的观测方法,可以发现数据仍然是测试点1的样子,就是顺序乱了。显然从任意一个点处绕一下就好了。

测试点3

线段这么少,而且每条就那么一点点,实在短的可怜,而它们之间的距离又是辣么大,根据物理学规律,把它们看做质点,然后枚举全排列就好。

测试点4

和测试点3几乎一样,只不过枚举不完所有的全排列,因此需要指数级的DP,当然也可以像我这种弱菜一样random_shuffle()一个小时。

测试点5

貌似只能无限random_shuffle()了。。。跪求出题人标算。。。

当然你用什么奇怪的贪心也是有可能骗到分的。

测试点6 10

考场上没有怎么看,不太记得了。。。

似乎要一些先进的网络流姿势,然后块内网络流,块间DP?

毕竟没有数据,在这里讲也没什么意思,据说mzx大爷拍下了讲题的PPT,你们可以去%……

然后就发现一个人怎么可以崩到这种程度……

(据说面试线180?据说参考了NOIP/WC/省选/APIO/CTSC?反正不知道死于什么)

怎么说呢?感觉考场上的心态不是很好啊。通读题目过后有点懵,好不容易搞出来A题就稍微高兴一点,但是又觉得A题这么难后面的肯定都不可做,就只想着暴力骗分了。。。

还是自信不够咯,毕竟入坑以来就没有过一场像点样子的比赛,不是这里挂就是那里挂,明明可以甩开别人,却因为细节被完虐了。。。

吃过这次的亏,希望在NOI上好一点吧。

一个人的成绩,当然要靠自我奋斗,但是也要考虑到历史的行程。——某国家领导人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值