2020.07.15【NOIP提高组】模拟

2020.07.15比赛总结

最大配对

题目大意:给出\(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]]|\),现在任务也就是最大化这个答案。

这题非常的简单,只要你会快排,你就能立马想出来,用最大减最小。

#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
int a[100005],b[100005],n,k;

int main()
{
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++)scanf("%d",&a[i]);
	for (int i=1;i<=n;i++)scanf("%d",&b[i]);
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	int l1=1,r1=n,l2=1,r2=n;
	long long ans=0;
	for (int i=1;i<=k;i++)
	{
		if (abs(a[l1]-b[r2])>abs(a[r1]-b[l2]))
		{
			ans+=(long long)abs(a[l1]-b[r2]);
			r2--;
			l1++;
		}
		else
		{
			ans+=(long long)abs(a[r1]-b[l2]);
			r1--;
			l2++;
		}
	}
	printf("%lld\n",ans);
}

旅行

题目大意:教主要带领一群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)作为子矩阵的右下角,会有多少类不同的资源被教主认为是稀有资源。
 为了照顾Vijos脑残的输出问题,设B[i, j]表示仅包含前i行与前j列的子矩阵有多少个数字恰好出现一次,那么你所要输出所有B[i, j]之和mod 19900907。

正解DP
#include<cmath>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
int n,a[5000],b[5000],f[5000][3];

int main()
{
	scanf("%d",&n);
	scanf("%d",&a[1]);
	for (int i=2;i<=n;i++) scanf("%d",&a[i]),b[i]=b[i-1]+abs(a[i]-a[i-1]);
	a[n+1]=a[n];
	memset(f,120,sizeof(f));
	f[n][0]=f[n][1]=0;
	for (int 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]));
		for (int 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])+b[j]-b[i+1]);
		f[i][1]=min(f[i][1],b[n]-b[i+1]+abs(a[i]-a[n]));
	}
	printf("%d",min(f[1][0],f[1][1]));
}

资源勘探

#include<cstdio>
#include<cstring>
using namespace std;
const int P=19900907;
int n,m,x[2000005],y1[2000005],y2[2000005],ans=0;

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	for (int j=1;j<=m;j++)
	{
		int q;
		scanf("%d",&q);
		if (!x[q]) x[q]=i,y1[q]=j,y2[q]=m+1;
		else
		{
			if (j<y1[q]) ans=(ans+(i-x[q])*(y2[q]-y1[q]))%P,x[q]=i,y2[q]=y1[q],y1[q]=j;
			else if (j<y2[q]) ans=(ans+(i-x[q])*(y2[q]-j))%P,y2[q]=j;		
		}
	}
	for (int i=1;i<=n*m;i++)
		if (x[i]) ans=(ans+(n+1-x[i])*(y2[i]-y1[i]))%P;
	printf("%d\n",ans);
}

排列统计

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

这题考虑B序列的差值,非常神奇,\(B_i -B_{i-1}=0,1,2\)

\(f_i\)表示到第\(i\)个数时的方案数
情况\(1\)\(B_i - B_{i-1}=0\),因为\(i\)没有贡献,所以\(f_i=f_{i-1}\)
情况\(2\)\(B_i - B_{i-1}=1\),这时说明要么第\(i\)个数是\(\leq i\),要么\(1\) ~ \(i-1\)中有一数是\(=i\),这是加法原理,所以\(f_i=f_{i-1}*[(i-B_{i-1})+(i-B_{i-1}-1)]\)
情况\(3\)\(B_i - B_{i-1}=2\),这时说明第\(i\)个数是\(\leq i\)\(1\) ~ \(i-1\) 中有一数是\(=i\)都有,这是乘法原理,所以\(f_i=f_{i-1}*(i-B_{i-1}-1)^2\)
最后\(ans=f_n\)

但是,要用高精度。

#include<cstdio>
using namespace std;
int f[50000];
int n,a[50000],l;

void mtlt(int k)
{
	int m=0;
	for (int j=1;j<=l;j++)
	{
		f[j]=f[j]*k+m;
		m=f[j]/10;
		f[j]=f[j]%10;
	}
	while (m)
	{
		f[++l]=m;
		m=f[l]/10;
		f[l]=f[l]%10;
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[1]=1,l=1;
	for (int i=2;i<=n;i++)
	{
		if (a[i]-a[i-1]==1) mtlt(i-a[i-1]+i-a[i-1]-1);
		else if (a[i]-a[i-1]==2)mtlt((i-a[i-1]-1)*(i-a[i-1]-1));
	}
	for (int i=l;i>=1;i--) printf("%d",f[i]);
}
posted @ 2020-07-16 07:18  RiverSheep  阅读(183)  评论(0编辑  收藏  举报