jzoj NOIP2014提高组模拟8.9总结

一、最大配对

 给出2个序列A={a[1]a[2]a[n]}B={b[1]b[2]b[n]},从AB中各选出k个元素进行一一配对(可以不按照原来在序列中的顺序),并使得所有配对元素差的绝对值之和最大。 
  例如各选出了a[p[1]]a[p[2]]……a[p[k]]b[q[1]]b[q[2]]……b[q[k]],其中p序列中的元素两两不相同,q序列中的元素两两不相同,那么答案为|a[p[1]]b[q[1]]||a[p[2]]b[q[2]]|……|a[p[k]]b[q[k]]|,现在任务也就是最大化这个答案。

可证得:要想配对数间差的绝对值之和最大,一定是最大值与最小值配对。

所以可以先排序,但由于最大值、最小值均可出自A、B序列中,且不可同时出自同一序列,互不影响,所以可以贪心一下,最后求和输出。

一般第一题都是比较简单的搜索、找规律或贪心题,而贪心一般建立在找规律上才可确保它没有后效性,此次第一题很多人说不出为什么贪心没有后效性,就大胆猜测,然而往往不是这么容易蒙对这种“显然”,故可以先从规律入手。(虽然我也是大胆猜测……)

二、旅行

(做这道题时莫名想起上次被师弟们虐的情景)

今天又是个神圣的日子,因为LHX教主又要进行一段长途旅行。但是教主毕竟是教主,他喜欢走自己的路,让别人目瞪口呆。为什么呢,因为这条路线高低不平,而且是相当的严重。 
  但是教主有自己的办法,他会魔法。 
  这段路可以用一个长度为n的序列A[i]来表示,A[i]表示了第i这段路的高度。毕竟教主即使会使用魔法他还是个人,教主如果想穿越这条路线,他必须从第1段路开始走,走到第n段,从第i段走到第i+1段路需要消耗|A[i+1]-A[i]|点体力。为了节省体力,教主使出了他另一种神奇的魔法。教主的魔法可以交换相邻两段路的高度,并且这种魔法不需要花费额外的体力。但是第二次使用魔法开始,交换的两段路在路线中的位置需位于之前交换的两段路之后。即如果某次交换了A[j]A[j+1],那么下次交换A[k]A[k+1]必须满足jk 
  接着,LHX教主想规划下如何调整路段高度后穿越,使得体力消耗最小。

通常遇到这类可通过修改,形成多方案求最优,且修改没有后效性的题目,都可以用dp打

Dp有顺推和逆推,可根据具体情况分析,众人表示逆推编程难度低,而在此我仅说明顺推的思路(一开始就这么打,只是很难说明构思,努力说明……)

首先定义f[i][j][k](1<i<n,j为传递过来的山的高度,k=0或1,表示当前山是否已经和后面的山交换,i可以用滚动数组,故空间完全没问题),我们确定前i座山的方案,后面的位置不变,分不操作和i+1与i+2交换后,用f[i]推f[i+1],可以得出的是,无论当前山是否交换,都可以确定第i座山和第i+1座山的其中一座高度为A[i+1],而由于可能有从前面传递过来一个无法得知的高度,这时候j就派上用场了。

分四中情况:

1、k=0 i+1与i+2不交换,则高度依次为:j、A[i+1]、A[i+2]把传递高度改为A[i+1]

2、k=0 i+1与i+2交换,则高度依次为:j、A[i+2]、A[i+1]传递高度为A[i+1]

3、k=1 i+1与i+2不交换,则高度依次为:A[i+1]、j、A[i+2]传递高度为j

4、k=1 i+1与i+2交换,则高度依次为:A[i+1]、A[i+2]、j 传递高度为j

知道具体高度后就可以直接推了(注:对于i=1时传递高度为A[i])最后再取最优值。

由于直接枚举j会达到n*maxA[i]的复杂度,所以可以用记忆化+循环队列优化时间复杂度为方案数。(具体可参照代码:

#include<cstdio>

#include<iostream>

#include<algorithm>

#include<cmath>

using namespace std;

int n,sum,l,r;

int f[2][100001][2],bz[100001],d[10000],a[2001];

void did(int x){

if (bz[x]!=sum){

bz[x]=sum;r=(r+1)%10000; d[r]=x;

}return;

}

int min(int x,int y){

if (x==0)return y;

if (x<y)return x;elsereturn(y);

 }

int main(){

 l=0;r=1;scanf("%d",&n);

 for(int i=1;i<=n;i++)

scanf("%d",&a[i]);

f[0][a[0]][0]=1;

bz[a[0]]=1;

sum++;

d[1]=a[0];

for (int i=0;i<=n-1;i++)

{ int x=i&1;

int x1=(i+1)&1;

int r1=r;

sum++;

while (l!=r1){

l=(l+1)%10000;

if (f[x][d[l]][0]!=0){

if (i!=0) f[x1][a[i+1]][0]=min(f[x1][a[i+1]][0],f[x][d[l]][0]+abs(d[l]-a[i+1]));else f[x1][a[i+1]][0]=1; did(a[i+1]);

if (i<=n-2){

if (i!=0)f[x1][a[i+1]][1]=min(f[x1][a[i+1]][1],f[x][d[l]][0]+abs(d[l]-a[i+2]));else f[x1][a[i+1]][1]=1;

}

 } if(f[x][d[l]][1]!=0){

 f[x1][d[l]][0]=min(f[x1][d[l]][0],f[x][d[l]][1]+abs(a[i+1]-d[l]));

 did(d[l]);

if (i<=n-2)f[x1][d[l]][1]=min(f[x1][d[l]][1],f[x][d[l]][1]+abs(a[i+2]-a[i+1]));

}

 f[x][d[l]][0]=0;

f[x][d[l]][1]=0;

}

}

int ans=0;

int x=n&1;

while (l!=r){

 l=(l+1)%10000;

if (f[x][d[l]][0]!=0)ans=min(ans,f[x][d[l]][0]);

if (f[x][d[l]][1]!=0)ans=min(ans,f[x][d[l]][1]); }

printf("%d",ans-1);

return 0;

})

这种顺推是无意间出现在脑中的,很难表述,但很多时候这类因人而异的思维模式会有助于个人的探究,所以believe yourself,一开始我是只得了50points,但后面坚信自己的思路,就改出来了。

附录:还有一种没打过的贪心搜索思路共享一下:其实可以发现,一段路的代价就是相邻波峰、波谷极值差的绝对值之和,而属于有效(即优化了方案)的交换,就是将某个峰顶交换到一座比它高的山里(或谷底交换到比他低的谷里),由于是只能单向交换,那么前面的山交换过的区间里的山就不能交换,只会对该山产生影响(如图:

两次的方案代价为蓝色高度和

)而且易证单操作下将峰顶(或谷底)移至最近的山(或谷)中为该操作下最优解,那么可以用dp或搜索+贪心(即要么移,移动的话移至最近,反之不移……据狗礼提议,这有点像区间覆盖,好机油们可从此方向继续打石油)进行求解,复杂度取决于峰顶和谷底的个数,由于只能移至比峰顶高的山或比谷底低的谷中,对数据的限制较多,所以复杂度应该很难出到极限,估测是可以过的。

三、资源勘探

 教主要带领一群Orzer到一个雄奇地方勘察资源。 
  这个地方可以用一个n×m的矩阵A[i, j]来描述,而教主所在的位置则是位于矩阵的第1行第1列。
  矩阵的每一个元素A[i, j]均为一个不超过n×m的正整数,描述了位于这个位置资源的类型为第A[i,j]类。教主准备选择一个子矩阵作为勘察的范围,矩阵的左上角即为教主所在的(1, 1)。若某类资源k在教主勘察的范围内恰好出现一次。或者说若教主选择了(x,y)即第x行第y列作为子矩阵的右下角,那么在这个子矩阵中只有一个A[i,j]1≤i≤x1≤j≤y)满足A[i, j]=k,那么第k类资源则被教主认为是稀有资源。 
  现在问题是,对于所有的(x, y),询问若(x, y)作为子矩阵的右下角,会有多少类不同的资源被教主认为是稀有资源。

一开始时着实没有好方法,我从点入手,看他向左向上覆盖的区间内的数出现一个的有多少(暴力一枚)(n^4)

然后我们可以发现某行答案与上行答案不同时,是因为该行出现了限制点,那么就可以出现以线入手,按行加入答案(n^3)

其实我们可以把答案分布图画出来,就可发现答案是以限制点作分割线,成块状分布的:

(举例出现一次1的,答案为红色区)

2 2 1 5 6 5

2 3 2 4 2 3

2 3 4 2 1 3

3 1 5 2 4 5

12 5 3 5 7

2 1 2 5 3 4

而且从没有覆盖的1,可看出起限制作用的点(一行一行推的话)是该行包括以前最偏左的两个1,只有一个1时,右限制为最后一列,反之为已枚举的1里最小和次小的纵坐标,答案就夹在里面,形成纵坐标区间,当然,这样还是按行加入的“暴力”,关键在于块状分布,因而可以再记录上一次出现的横坐标,与当前列举出现该数的行形成横坐标区间,加上纵坐标区间,就可形成一个块状的答案区,然后O(1)加入答案,时间复杂度为O(n^2),注意:有的数字可能在最后一行没列出,其与最后一行形成的答案区会没记上,所以要在输出前将其答案补上。

四、排列统计

对于给定的一个长度为n的序列{B[n]},问有多少个序列{A[n]}对于所有的i满足:A[1]A[i]i个数字中有恰好B[i]个数字小等于i。其中{A[n]}1n的一个排列,即1nn个数字在序列A[I]中恰好出现一次。 
  数据保证了至少有一个排列满足B序列。

看到这题,着实没啥想法,但看到网上神转换,着实惊呆了:

设定一个矩阵f

若我们将横坐标x表示位置,纵坐标y表示数值,用1和0表示是否有数,若f(x1,y1)=1,则表示第x1个数为y1,由于1~n只能出现一次,每个位置只能出现一个数,所以同行同列只能出现一个1。

这时我们来看看B的定义,为位置小于等于i,数值小于等于i的数个数限制,若装换到f中,则为B[i]=sig(f(x,y)(0<x<=i,0<y<=i)),即以(i,i)、(1,1)为顶点的矩阵中的1的个数限制。那么现在我们可以沿该矩阵对角线进行递推,由于同行同列只有一个1,那么根据矩阵的模型来看,可知B[i]-B[i-1]只可为0、1、2,反之无解,

       因而我们可以矩阵f(1~i-1,1~i-1)方案确定的情况下来确定f(1~i,i)和f(i,1~i-1)的方案,然后用乘法原理来进行合并,可看作一个反向的L。如图:

       01

1 0

???

然后分三种情况:B[i]-B[i-1]=0,则该“L”中不放1,方案数不变; B[i]-B[i-1]=1,则要在该“L”中放1个1,由于横坐标、纵坐标分别已占了B[i-1]个位置,可用位置为((i-b[i-1])*2-1) B[i]-B[i-1]=2,则要在该“L”中放2个1,则(i,i)上不能为1,则两个1分别可放(i-b[i-1]-1)个位置,方案为(i-b[i-1]-1)^2。(注:我们不必确定具体位置,只需确定可行位置数,我们可看成不用的在后面会填上(∵在n*n的矩阵里放n个1,每行每列必定有一个1),也可看成只要确定了有可行位置,那么必定会组合成一个合法解)

由此可见,对数值、位置、大小、等级、顺序建立纬度,可形成一个n维模型,更直观体现要求,也更容易求解(即便解题方案没什么差别,但拥有一个良好的思维构图,能让人在抽象的条件下,找到直观的立足点,以便更好解题)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值