纪中暑假集训 2020.07.15【NOIP提高组】模拟 反思+题解

这次比赛又挂了,140,第12(不过暑假来了几个巨佬,排名可能会适当往后拉一点 ) ,题目除了签到题其他难度和质量都很不错,思维很重要,方法懂了确实不算太难……收获颇丰!

T1:最大配对

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

【样例说明】

配对(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。

反思&题解

考试&正解思路: 一个数组从小到大排序,一个从大到小排序,之后指针判断头尾差的绝对值选k个就行了
反思: 水,不过签到题是绝对不能丢分的!!!

CODE

#include
using namespace std;
int n,k,a[100005],b[100005];
long long ans;
bool cmp(int x,int y)
{
	return x<y;
}
bool cmp1(int x,int y)
{
	return x>y;
}
int main()
{
	scanf("%d%d",&n,&k);
	int i;
	for (i=1;i<=n;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1,cmp);
	for (i=1;i<=n;i++)
		scanf("%d",&b[i]);
	sort(b+1,b+n+1,cmp1);
	int j;
	j=n;
	i=1;
	while (k)
	{
		if (abs(a[i]-b[i])>abs(a[j]-b[j]))
		{
			ans+=abs(a[i]-b[i]);
			i++;	
		}
		else
		{
			ans+=abs(a[j]-b[j]);
			j--;
		}
		k--;
	}
	printf("%lld\n",ans);
	return 0;
}

T2:旅行

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

【输入输出样例】

将位置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:
s u m [ i , j ] sum[i,j] sum[i,j]表示从 i i i j j j这段所耗费的体力,因为就算换了位置相对的一段的贡献都是一样的
f [ i ] [ 1 / 0 ] f[i][1/0] f[i][1/0]表示后 i i i位中第 i i i位换了或没换位置所耗费的最小体力
i i i从后往前枚举,方程如下:
1. f [ i ] [ 0 ] = m i n ( f [ i + 1 ] [ 0 ] + a b s ( a [ i ] − a [ i + 1 ] ) , f [ i + 1 ] [ 1 ] + a b s ( a [ i ] − a [ i + 2 ] ) ) f[i][0]=min(f[i+1][0]+abs(a[i]-a[i+1]),f[i+1][1]+abs(a[i]-a[i+2])) f[i][0]=min(f[i+1][0]+abs(a[i]a[i+1]),f[i+1][1]+abs(a[i]a[i+2]))
2. i < j < n ii<j<n时: f [ i ] [ 1 ] = m i n ( f [ i ] [ 1 ] , m i n ( f [ j + 1 ] [ 0 ] + a b s ( a [ i ] − a [ j + 1 ] ) , f [ j + 1 ] [ 1 ] + a b s ( a [ i ] − a [ j + 2 ] ) ) + a b s ( a [ i ] − a [ j ] ) + ( s u m [ j ] − s u m [ i + 1 ] ) ) f[i][1]=min(f[i][1],min(f[j+1][0]+abs(a[i]-a[j+1]),f[j+1][1]+abs(a[i]-a[j+2]))+abs(a[i]-a[j])+(sum[j]-sum[i+1])) f[i][1]=min(f[i][1],min(f[j+1][0]+abs(a[i]a[j+1]),f[j+1][1]+abs(a[i]a[j+2]))+abs(a[i]a[j])+(sum[j]sum[i+1]))
否则, f [ i ] [ 1 ] = m i n ( f [ i ] [ 1 ] , ( s u m [ n ] − s u m [ i + 1 ] ) + a b s ( a [ n ] − a [ i ] ) ) f[i][1]=min(f[i][1],(sum[n]-sum[i+1])+abs(a[n]-a[i])) f[i][1]=min(f[i][1],(sum[n]sum[i+1])+abs(a[n]a[i]))
反思: 看到怎么设状态就大概知道怎么做了,比赛时不能太局限于最普通的方法,要跳出固定思维,还有DP题要多练一下

CODE

#include
using namespace std;
int a[100005],n,sum[100005],f[100005][2];
int main()
{
	scanf("%d",&n);
	int i;
	for (i=1;i<=n;i++)
		scanf("%d",&a[i]);
	a[n+1]=a[n];
	memset(f,0x3f3f3f3f,sizeof(f));
	f[n][0]=0;
	f[n][1]=0;
	for (i=2;i<=n;i++)
		sum[i]=sum[i-1]+abs(a[i]-a[i-1]);
	for (i=n-1;i>=1;i--)
	{
		f[i][0]=min(f[i+1][0]+abs(a[i]-a[i+1]),f[i+1][1]+abs(a[i]-a[i+2]));
		int j;
		for (j=i+1;j<n;j++)
			f[i][1]=min(f[i][1],min(f[j+1][0]+abs(a[i]-a[j+1]),f[j+1][1]+abs(a[i]-a[j+2]))+abs(a[i]-a[j])+(sum[j]-sum[i+1]));
		f[i][1]=min(f[i][1],(sum[n]-sum[i+1])+abs(a[n]-a[i]));
	}
	printf("%d\n",min(f[1][0],f[1][1]));
	return 0;
}

T3:资源勘探

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

【样例说明】

对于右下角为(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。

反思&题解

比赛思路: 除了暴力还能干啥???
正解思路: 分类讨论,求出每个数字的贡献:
记录一下一个数字第一次出现的横纵坐标和它第二次出现的纵坐标,之后再扫描一次矩阵:
1. 纵坐标比第一次出现的还前,那么贡献就是前面的矩阵,则 a n s + = ( i − x [ t ] ) ∗ ( y y [ t ] − y [ t ] ) ; ans+=(i-x[t])*(yy[t]-y[t]); ans+=(ix[t])(yy[t]y[t]);
2. 纵坐标在第一次和第二次之间,则 a n s + = ( i − x [ t ] ) ∗ ( y y [ t ] − j ) ; ans+=(i-x[t])*(yy[t]-j); ans+=(ix[t])(yy[t]j);
3. 如果在第二次之后,就没有任何贡献,舍去
记得还要更新记录的坐标
最后, 每个数字还有第一次和第二次之间的要补上,不要忘记mod了
反思: 难,对于正解还是没有完全理解,需要继续琢磨

CODE

#include
using namespace std;
int n,m,x[1250000],y[1250000],yy[1250000],ans;
const int mo=19900907;
int main()
{
	scanf("%d%d",&n,&m);
	int i,j,t;
	memset(x,0,sizeof(x));
	memset(y,0,sizeof(y));
	for (i=1;i<=n*m;i++)
		yy[i]=m+1;
	for (i=1;i<=n;i++)
	{
		for (j=1;j<=m;j++)
		{
			scanf("%d",&t);
			if (x[t]==0)
			{
				x[t]=i;
				y[t]=j;
			}
			else
			{
				if (j>=y[t])
				{
					if (j<yy[t])
					{
						ans+=(i-x[t])*(yy[t]-j);
						ans%=mo;
						yy[t]=j;
					}	
				}
				else
				{
					ans+=(i-x[t])*(yy[t]-y[t]);
					ans%=mo;
					x[t]=i;
					yy[t]=y[t];
					y[t]=j;
				}
			}
		}
	}
	for (i=1;i<=n*m;i++)
	{
		if (x[i])
		{
			ans+=(n+1-x[i])*(yy[i]-y[i]);
			ans%=mo;
		}
	}
	printf("%d\n",ans);
	return 0;
} 

T4:排列统计

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

【样例说明】

对于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。

反思&题解

比赛思路: 继续暴力
正解思路: 递推,下面献上一位巨佬的官方题解!!!(高精度码得我腰疼)
纪中暑假集训 2020.07.15【NOIP提高组】模拟 反思+题解_第1张图片
反思: 一样还是理解得不是很透彻,继续钻研,加油!!!

CODE

//c++字符串模拟太恶心,所以用了pascal
//听说其他大佬用500bytes就打完了,为啥我用了2000多bytes!!!
var
        n,i:longint;
        b,a1,a2,sum:array[0..2005]of longint;
        f:array[0..2005]of ansistring;
procedure check1(k:longint);
var
        i,j,t,x:longint;
        s1:string;
begin
        fillchar(a1,sizeof(a1),0);
        fillchar(a2,sizeof(a2),0);
        t:=k-b[k-1]+k-1-b[k-1];
        str(t,s1);
        a1:=sum;
        a2[0]:=length(s1);
        fillchar(sum,sizeof(sum),0);
        for i:=1 to a2[0] do
                a2[a2[0]-i+1]:=ord(s1[i])-48;
        for i:=1 to a1[0] do
        begin
                x:=0;
                for j:=1 to a2[0] do
                begin
                        sum[i+j-1]:=a1[i]*a2[j]+x+sum[i+j-1];
                        x:=sum[i+j-1] div 10;
                        sum[i+j-1]:=sum[i+j-1] mod 10;
                end;
                sum[i+j]:=x;
        end;
        sum[0]:=i+j;
        while (sum[0]>1)and(sum[sum[0]]=0) do dec(sum[0]);
        for i:=sum[0] downto 1 do
                f[k]:=f[k]+chr(sum[i]+48);
end;
procedure check2(k:longint);
var
        i,j,t,x:longint;
        s1:string;
begin
        fillchar(a1,sizeof(a1),0);
        t:=sqr(k-b[k-1]-1);
        str(t,s1);
        a1[0]:=length(s1);
        for i:=1 to a1[0] do
                a1[a1[0]-i+1]:=ord(s1[i])-48;
        a2:=sum;
        fillchar(sum,sizeof(sum),0);
        for i:=1 to a1[0] do
        begin
                x:=0;
                for j:=1 to a2[0] do
                begin
                        sum[i+j-1]:=a1[i]*a2[j]+x+sum[i+j-1];
                        x:=sum[i+j-1] div 10;
                        sum[i+j-1]:=sum[i+j-1] mod 10;
                end;
                sum[i+j]:=x;
        end;
        sum[0]:=i+j;
        while (sum[0]>1)and(sum[sum[0]]=0) do dec(sum[0]);
        for i:=sum[0] downto 1 do
                f[k]:=f[k]+chr(sum[i]+48);
end;
begin
        readln(n);
        for i:=1 to n do
                read(b[i]);
        f[0]:='1';
        sum[0]:=1;
        sum[1]:=1;
        for i:=1 to n do
        begin
                if b[i]=b[i-1] then f[i]:=f[i-1]
                else if b[i]=b[i-1]+1 then check1(i)
                else if b[i]=b[i-1]+2 then check2(i);
        end;
        writeln(f[n]);
end.


你可能感兴趣的:(题解,反思)