NOIP2014提高组模拟8.9


最大配对 (Standard IO)


Description


  给出2个序列A={a[1],a[2],…,a[n]},B={b[1],b[2],…,b[n]},从A、B中各选出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]]|,现在任务也就是最大化这个答案。


Input


  输入的第1行为2个正整数n,k,表示了序列的长度和各要选出元素的个数。 
  第2行包含n个正整数,描述了A序列。 
  第3行包含n个正整数,描述了B序列。


Output


  输出仅包括一个非负整数,为最大的结果。 
  注意:答案可能超过2^31-1,请使用int64或者long long(若使用printf输出请用“%I64d”)类型储存结果。


Sample Input


4 2
2 5 6 3
1 4 6 7


Sample Output


10


Data Constraint




Hint


【样例说明】
  配对(2,7)、(6,1)结果为|2-7|+|6-1|=10。
【数据说明】
  对于10%的数据,有k≤5,n≤10;
  对于30%的数据,有n≤100; 
  对于50%的数据,有n≤1000;
  对于100%的数据,有k≤n≤100000;a[i],b[i]≤1000000。

这道题不用多说了,贪心即可。将A,B分别从小到大排序然后首尾贪心配对即为正解。还有人没注意Ans的数据类型而犯错,这种低级错误需引起人们的谨慎,打完程序要检查数据类型合理否,数组范围。


旅行 (Standard IO)

Description


  今天又是个神圣的日子,因为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]必须满足j<k。 
  接着,LHX教主想规划下如何调整路段高度后穿越,使得体力消耗最小。


Input


  输入的第1行为一个正整数n,表示了这条路线的长度。 
  第2行有n个正整数,相邻两个正整数用空格隔开,描述了A[i]这个序列。


Output


  输出仅包括一个非负整数,为最小的体力消耗。


Sample Input


4
2 3 4 1


Sample Output


4


Data Constraint




Hint


【输入输出样例】
  将位置1上的数字和位置2上的数字交换,序列变为3 2 4 1。 
  将位置2上的数字和位置3上的数字交换,序列变为3 4 2 1。 
  序列3 4 2 1需要消耗的体力为4。
【数据说明】
  对于10%的数据,n≤10; 
  对于20%的数据,n≤18; 
  对于50%的数据,n≤200; 
  对于100%的数据,n≤2000,A[i]≤100000。

一开始我首先想到了将数据离散化后DP,毕竟只有2000个A[i]值。但对交换的状态转移十分麻烦。最后还是用正解做了出来。


正解是从后往前推的,当然也可以从前往后,但从后往前没那么复杂。定义状态f[i][1]从第i个位置走到第n个位置,第i个位置换了的最优值。

[0]从。。。。。。。。。。。。。。。。没换。。。。。


那么F[i][0]=Min{F[i+1][0]+dis(i,i+1),

 F[i+1][1]+dis(i,i+2)//因为不论第i+1换到了哪,第i个总和第i+2个接触}

F[i][1]=Min{//假设将第i个换到j后面,那么从第i+1个到j的高度是不变的,还是连续整体,可以用前缀和搞定,设Si是1~i差的总和,k=Sj-Si+1,然后接上F[ j + 1]的转移即可。

F[i][1],

F[j+1][0]+abs(a[j+1]-a[i])+k+abs(a[j]-a[i]),

F[j+1][1]+abs(a[j+2]-a[i])+k+abs(a[j]-a[i])

}

这是我的AC程序::

#include
   
   
    
    
#include
    
    
     
     
#include
     
     
      
      
#include
      
      
       
       

using namespace std;

int s[2010],n,a[2010],f[2010][2],i,j,k;

int main(){
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=2;i<=n;i++)s[i]=s[i-1]+abs(a[i]-a[i-1]);
	memset(f,127,sizeof(f));
	f[n][0]=0;
	for(i=n-1;i>=1;i--){
		f[i][0]=min(f[i+1][1]+abs(a[i+2]-a[i]),f[i+1][0]+abs(a[i+1]-a[i]));
		for(j=i+1;j<=n-1;j++){
			k=s[j]-s[i+1];
			f[i][1]=min(f[i][1],min(f[j+1][0]+abs(a[j+1]-a[i]),f[j+1][1]+abs(a[j+2]-a[i]))+k+abs(a[j]-a[i]));
		}
		f[i][1]=min(f[i][1],abs(a[i]-a[n])+s[n]-s[i+1] );
	}
	printf("%d",min(f[1][1],f[1][0]));
}

      
      
     
     
    
    
   
   


反思:想状态不要想的太复杂了,经历过了好几道这种关于交换的题,状态肯定要带上换了还是没换。要吸取这次误用高度值Dp的教训。

Description


  教主要带领一群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≤x,1≤j≤y)满足A[i, j]=k,那么第k类资源则被教主认为是稀有资源。 
  现在问题是,对于所有的(x, y),询问若(x, y)作为子矩阵的右下角,会有多少类不同的资源被教主认为是稀有资源。


Input


  输入的第一行包括两个正整数n和m,接下来n行,每行m个数字,描述了A[i, j]这个矩阵。


Output


  为了照顾Vijos脑残的输出问题,设B[i, j]表示仅包含前i行与前j列的子矩阵有多少个数字恰好出现一次,那么你所要输出所有B[i, j]之和mod 19900907。


Sample Input


2 3
1 2 3
3 1 2


Sample Output


10


Data Constraint




Hint


【样例说明】
  对于右下角为(1,1)的子矩阵,仅包含数字1,所以答案为1。 
  对于右下角为(1,2)的子矩阵,数字1、2各出现一次,所以答案为2。 
  对于右下角为(1,3)的子矩阵,数字1、2、3各出现一次,所以答案为3。 
  对于右下角为(2,1)的子矩阵,数字1、3各出现一次,所以答案为2。 
  对于右下角为(2,2)的子矩阵,数字2、3各出现一次,但是数字1出现了两次,所以数字1不统计入答案,答案为2。 
  对于右下角为(2,3)的子矩阵,数字1、2、3均出现了两次,所以答案为0。
【数据说明】
  对于10%的数据,有N,M≤10; 
  对于20%的数据,有N,M≤20; 
  对于40%的数据,有N,M≤150; 
  对于50%的数据,A[I, J]≤1000; 
  对于70%的数据,有N,M≤800; 
  对于100%的数据,有N,M≤1100,A[I, J] ≤N×M。

这道题我考试时没深思过,因为时间全浪费在了第二题上。

由于稀有资源要求在子矩阵出现仅出现一次,那么对于矩阵,我们应把行列割离开来分析。

对于列,想知道一种资源稀有了几次,我们应想到稀有次数是在之前或当前行的第一次出现与第二次出现之间的区间。也就对于每行维护一种资源的最小出现时值和次小出现值。

对于行,只要记录下每次资源变更最小次小值的行号,最后统计即可。

我用了链表维护这个结构,记录每种资源最小次小每次变更行号。最后统计出现了多少次。


#include
   
   
    
    
#include
    
    
     
     

struct ecc{
	int se,fi,ln,la;
}b[1110*1100];

int a[1101][1101],n,m,i,j,k,g[1210*1200],tot;//g为链表头指针,b.se为次小值,b.fi为最小值,b.ln行号,b.la为上一次的更新的地址
bool p;
int main(){

	scanf("%d%d",&n,&m);
	k=0;
	b[0].se=m+1;b[0].fi=m+1;
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++){
			scanf("%d",&a[i][j]);p=0;
			if(i!=b[g[a[i][j]]].ln)++tot,k=tot,p=1;else k=g[a[i][j]];
			if(j<=b[g[a[i][j]]].fi){                    //更新最小次小值
				b[k].se=b[g[a[i][j]]].fi;
				b[k].fi=j;
				if(p)b[k].la=g[a[i][j]],g[a[i][j]]=k;
				b[k].ln=i;
			}else if(j<=b[g[a[i][j]]].se){
				b[k].se=j;
				b[k].fi=b[g[a[i][j]]].fi;
				if(p)b[k].la=g[a[i][j]],g[a[i][j]]=k;
				b[k].ln=i;
			}
		}
	}
	long long ans(0);int la;
	for(i=1;i<=n*m;i++){ //统计次数
		k=g[i];la=g[i];
		ans+=(n-b[la].ln+1)*(b[la].se-b[la].fi);//对于最后一次更新,b.ln~n行的最小次小区间的点为矩阵右下角的点的矩阵都是可行解
		ans%=19900907;
		if(!k)continue;
		while(k){
			if(k!=g[i])ans+=(b[la].ln-b[k].ln)*(b[k].se-b[k].fi),ans%=19900907;//计算每次更新间的答案
			la=k;
			k=b[k].la;
		}
	}
	printf("%lld",ans);
}

    
    
   
   

反思::代码前的解题思路是我想Ac这道题的人的思维顺序应当这样,表述的有点ws,对于这类模拟题,要深入理解问题实质。



排列统计 (Standard IO)


Description


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


Input


  输入的第1行为一个正整数N,表示了序列的长度。 
  第2行包含N个非负整数,描述了序列{B[i]}。


Output


  输出仅包括一个非负整数,即满足的{A[i]}序列个数。


Sample Input


3
0 1 3


Sample Output


3


Data Constraint




Hint


【样例说明】
  对于A序列为1~3的全排列分别对应的B序列如下(冒号左边为A序列,冒号右边为对应B的序列) 
  1 2 3:1 2 3 
  1 3 2:1 1 3 
  2 1 3:0 2 3 
  2 3 1:0 1 3 
  3 1 2:0 1 3 
  3 2 1:0 1 3 
  所以有3个满足的A序列。
【数据说明】
  对于20%的数据,有N≤8; 
  对于30%的数据,有N≤11且答案不大于20000; 
  对于50%的数据,有N≤100; 
  对于100%的数据,有N≤2000。
由于它保证有解,所以b[i]-b[i-1]<=2.

分类讨论b[i]-b[i-1]=1 ;ans=(2*i-1-2*b[i-1])*ans

2;ans=sqr(i-1-b[i-1])*ans


要用压8位高精乘实现,我的代码比较龊,就不复上来了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值