本文笔者完成于2017年3月份,当时水平有限,可能也有颇多疏漏,若有问题尽请指出,本人一定更正。
近来,考了几套NOIP真题,另外伟大的斯堪福·胡也给考了几道,余以为甚为有趣。之前已经写了一些总结,但总还认为不够。
20170218 |
NOIP2010 |
Translate 100 |
Tortoise 0 |
Prison 0 |
Flow 30 |
20170224 |
两道DP |
Salesman 100 |
Power 50 |
|
|
20170225 |
NOIP2009 |
Word 100 |
Matches 100 |
Message 100 |
Twostack 30 |
20170303 |
NOIP2005的一半 |
River 10 |
Fire 80 |
|
|
20170304 |
NOIP2008 |
Spy 100 |
Son 0 |
Trade 0 |
Sudoku 5 |
20170310 |
NOIP2011D1 |
Carpet 100 |
Hotel 10 |
Mayan 10 |
|
##**20170218 NOIP2010**
话说这套题,第一遍做得很不好,只有130。所以说,圣人云,不以物喜,不以己悲。把握好状态,才能稳健地走下去。
Translate这道题,不过是循环队列+模拟。但要注意,题干中是查询有没有,若没有,就计数填入。填入是先填满,填满了再先进先出。有一些同志没有意识到填入的可能是0(非负数),容易与空位混淆,从而出现错误。
Tortoise这道题,是一道**典型**的DP题。因为只有四种牌,每种牌的数目都已给出,且用完就刚好到终点。于是可以使用四维DP,每种牌不超过40,而40^4=2560000是一个游刃有余的数。
注意,所谓典型,是因为我们可以清晰的找出无后效性与各种状态,一点是很有必要的。
Prison是一道很优秀的图论入门题目,可以用多种角度思考。
**法1**:二分答案+二分图染色**O(nlogn)**
注意到题目要求将犯人关进2个监狱,使最大仇恨值最小。于是我们可以二分答案出满足条件的最大仇恨值,每一次二分,图中只剩下边权大于等于最大仇恨值的边。而我们所要做的事,即是DFS二分图染色加以判断,最后出最优解。
**法2**:排序+并查集**O(nlogn+n)**
注意到题目要求将犯人关进2个监狱,使最大仇恨值最小。请想象,面对一堆犯人,我们肯定会先拆开仇恨值最大的一对,并在此基础上继续拆下去,知道我们无能为力,那这就是答案。我们可以使用并查集,同组者很好处理,但我们会刻意拆开,怎么记录呢?我们可以使用“反人”(k的反人是n+k)。
但是,条件是乱的,所以需要排序。复杂度与法1相当,但代码短多了。而且,如果条件有序,就真的是**O(n)**的了。
Flow引水入城大概是我目前遇过的(笔者在当时的水平有限)最好的综合题,可以DFS判断是否可以并将其化为区间覆盖问题,再DP求解。
注意到,最后得到的结果一定是连续区间覆盖。
##**20170224 两道DP**
Salesman是当时学状压DP的例题TSP问题,相当于复习。状压对时间的节省是非凡的,但占空之大(2^21=2097152)是很吓人的。一道真正的状压是藏不住的,因为数据范围太小了。
不过,据说有一种叫做退火的神奇算法……
power是当时学简单DP的例题,但当时是**O(n^3)**,明显会爆。我们需要用**O(n^2)**的复杂度,但怎么办呢?鉴于题目的性质,人是如洋葱般一层层的往外走,固必为连续区间,此时我们可以考虑区间DP。
但是我们在实现时,发现二维DP不够,还需半维,因为一个区间会有两个端点,最后也有可能分别停在两个末端点。
##**20170225 NOIP2009**
Word模拟,找字符串中字母出现的最高频率与最低频率(注意处理0),作差判断质数(注意0不是质数)。
matches记忆化搜索(因n<=24不可能凑出四位数加法)或打表(n<=24,但事实证明根本不需要)。
Message是方格取数的姊妹版,但需要注意不能重复经过同一个点。但幸好,好感度不为负,重复经过同一个点必非最优,从而让我这个逻辑不严密的家伙逃过一劫。但怎么避免重复经过同一个点呢?方案如下:
先memset( dp, -127, sizeof( dp ) );再循环,确保所更新状态的两个点满足i != x && j != y,同时不从重复的点过来。
但注意到,此题用四维太不优,因为我们会强制两条路同步往下走(i+j=x+y),于是可以由i,j,x推及y,从而省去一维。
Twostack双栈排序,运用到了一个很重要的栈的性质:单调性。
再进一步,于是便有结论1:若i<j<k,而aa[k]<aa[i]<aa[j],则i、j不可同栈;
再进一步,于是便有结论2:若i<j,而min(aa[j+1]~aa[n])<aa[i]<aa[j], 则i、j不可同栈(DP思想);
验证结论1,是**O(n^3)**;而验证结论2,是**O(n^2)**。
f[n+1] = OO;
for( int i = n; i >= 1; i-- )
f[i] = min(f[i+1],aa[i]);
for( int i = 1; i < n; i++ )
for( int j = i + 1; j <= n; j++ )
if( aa[i] < aa[j] && f[j+1] < aa[i] )//出现逆序不可处于同栈
insert( i, j ),insert( j, i );//注意双向建边
在此基础上,建出矛盾图,再从1开始,二分图染色(**好像prison**),注意优先放栈A。最终,模拟一遍即可。
##**20170303 NOIP2005的一半**
2005年的题,话说是很有趣的。
River是一道用数据范围卡智商的典型DP题。因为它的数据与题目描述跟NOIP 2015 T2 D1 stone颇为相似,让许多少年都想到去用二分答案,从而误入歧途。
而我呢,听了lmy、wyy的“一面之词”,竟以为可以直接用贪心,从而只得了10分(其实对贪心已经够了)。但是,说River用数据范围卡智商,该怎么办呢?
考虑到题目性质,L很远(1 <= L <= 10^9),但石子总数很少(1 <= M <= 100),而每跳一步限制了[S,T](1 <= S <= T <= 10)的范围,所以说任意两石子间可以缩短到至多2*t(其余的直接大跳毫无影响)。
1 if( aa[i] - aa[i-1] > t )
2 dis[i] = dis[i-1] + ( aa[i] - aa[i-1] ) % t + t;
3 else dis[i] = dis[i-1] + aa[i] - aa[i-1];
通过此方法,L可以压至最多M*2*T<=2000,这就毫无压力了,成为了一道弱智的DP题。所以,**模型的转换和技巧是很重要的**,看似很辽阔的距离,可以霎时化作咫尺,但一定要理清思路。
Fire此题更有趣,先造环,再用两个环判断最大居原位者,但判断是否成环也颇有技巧。而我在造环是所犯的看似极小却致命的错误,居然让我得了80分,让我不禁怀疑数据……
##**20170304 NOIP2008**
Spy这道题当时打着真心够呛(因为斯堪福大人叫我们手打数据……)。题目很简单,但要注意映射中对不可行的判断,这意味着你是否读懂了题。**所以,审题很重要。**
Son是一道数论题,找满足要求的x的个数(lcm(x,a0)=b0,gcd(x,a1)=b1)。分解质因数就够了。但当时思路未通透,所以WA完了。**所以,审题很重要。**
Trade有多种方法,是一道经典的“图上DP”(lmy如是说)。
**法1:O(n)**该题是要选择一条道路(从1到n),低入高出求最大价格差。该题有单向边也有双向边,且可重复经过同一个点。所以,需要进行一定处理。
我们可以先使用tarjan缩一下点,再DFS一遍,注意最值取强连通分量的。
**但不知为何,本人至今用这个方法都只有70……**
**法2:O(kE)**该题既然是找路径最值,低入高出,便可以使用SPFA。从1开始正着搜(正图)处理最小值,再从n开始倒着搜(反图)处理最大值。
**但是lmy有言,我可以卡SPFA……**
Soduku是一道很有价值的暴力题,很考耐心的。为了优化,可以使用二进制位运算(**O(1)判断某位置可填的数**)。
##**20170310 NOIP2011D1**
Carpet正着读反着判简单模拟题。
Hotel有n种方法。
**法1:**张杰尘式DP,思路较含混但也有一定巧妙之处。**O(n)**
**法2:**王驭洋式DP,复杂度较高且是二维的但不会超时。**O(nk)**
**法3:**线段树区间查询,因为不用写modify所以还是比较简洁。**O(n^2+优化)**
注意:法2完全可以用前缀和代替。
**法4:**我的DP。直接上代码。**O(n)**
1 readin(n);readin(k);readin(p);
2 for( int i = 1; i <= n; i++ ) {
3 readin(color);readin(price);
4 if( ntot[color] < tot || price <= p ) { //pre[color]与now[color]互化
5 pre[color] += now[color];
6 now[color] = 0;
7 ntot[color] = tot;
8 }
9 now[color]++; // pre[color]与now[color]之和是每次+1的
10 ans += pre[color]; //pre[color],当前客栈之前可以进行配对的个数
11 if( price <= p ) tot++;
12 }
13 printf( "%d
", ans );
看起来还是很简洁的,个人以为思路也比较清晰。只是考试时忘了加上“|| price <= p”这一段,直接丢去90分。原则上,这是对前缀和的极度优化。
用pre[color]来表示当前客栈之前可以进行配对的个数,now[color]来表示当前客栈之前不能进行配对的个数。可以确定的是,pre[color]与now[color]之和是每次+1的,但他们之间存在着互化的关系。这是很有趣的,也需要把思路弄清楚。
**而我呢,就是没有过多审题,心中一片杂乱。**
**法5:** 李明洋式DP,找全集与补集。若无最低消费限度,应当是全集。但中间有一些会失效,即是找补集。全集-补集=答案**O(n)**
1 readin(n),readin(k),readin(p); 2 for(register int i=1;i<=n;i++){ 3 readin(col[i]),readin(tmp); 4 if(tmp>p)flag[i]=1; 5 } 6 for(register int i=1;i<=n;i++){ 7 if(flag[i]==0){ 8 memset(cnt,0,sizeof(cnt)); 9 continue; 10 11 } 12 cnt[col[i]]++; 13 _count+=(cnt[col[i]]-1); 14 } 15 memset(cnt,0,sizeof(cnt)); 16 for(register int i=1;i<=n;i++){ 17 cnt[col[i]]++; 18 sum+=(cnt[col[i]]-1); 19 } 20 cout<<sum-_count<<endl;
全集、补集的找法都是相似的,但所对应区间不同,补集对应的是一段连续都超过最低消费的区间(一定在此之内,因为外面的点是可以与里面的点对应的)。
**注:register int似乎并无大用。 **
而Mayan一如既往,仍然是一道模拟搜索暴力题,是很考耐心的。我们首先可以创建一个class类,简明的实现游戏的功能(这就成功了一大半),然后再在外面套上搜索。十分美观,且可以自己改成真正的游戏。