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
",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
",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]);
}