总结(20170218~20170310)

  本文笔者完成于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\n", 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类,简明的实现游戏的功能(这就成功了一大半),然后再在外面套上搜索。十分美观,且可以自己改成真正的游戏。

posted @ 2017-10-11 14:29  Doggu  阅读(209)  评论(0编辑  收藏  举报