Description
假定给出一个包含N个整数的数组A,包含N+1个整数的数组ID,与整数R。其中ID数组中的整数均在区间[1,N-1]中。
用下面的算法对A进行Warshall-Turing-Fourier变换(WTF):
sum = 0 for i = 1 to N
index = min{ ID[i], ID[i+1] }
sum = sum + A[index]
将数组A往右循环移动R位
将数组A内所有的数取相反数,这个东西是在循环外面
for i = 1 to N
index = max{ ID[i], ID[i+1] }
index = index + 1
sum = sum + A[index]
将数组A往右循环移动R位
给出数组A以及整数R,但没有给出数组ID。在对数组A进行了WTF算法后,变量sum的可能出现的最大值数多少?
Input
第一行包含两个整数N与R。
第二行包含N个整数,代表A[1]到A[N]的值。
Output
第一行输出变量sum可能出现的最大值。
第二行输出此时的ID数组,包含N+1个整数。必须满足ID数组中每一个数均在区间[1,N-1]中。若有多个满足的ID数组,输出任意一组。
如果第一行是正确的(不管有没有输出第二行),你能得到该测试点50%的得分。
Sample Input
输入1:
5 3
1 -1 1 -1 1
输入2:
6 5
2 5 4 1 3 5
Sample Output
输出1:
10
1 1 1 2 2 3
输出2:
16
3 2 1 1 5 4 1
Data Constraint
对于20%的数据,N<=7。
对于60%的数据,N<=300。
对于100%的数据,2<=N<=3000, 1<=R<N, -10000<=A[i]<=10000。
这个题还是比较恶心而且有点参考性的,这里就写的详细一点吧。
正解
- 首先呢,这个题面要先看懂,一开始我以为每次循环后都要变一下相反数,所以感觉样例都是错的。
- 然后这个题是个dp啊
- 我们先设f[i][j]表示第i个位置选j时最大sum
- 我们要知道,当a数组右循环r个数,这样做n次时,a数组时与一开始一样的,也就是题面上的两个循环刚开始时a都是初始的a
- 我们还有一个东西叫get,这个是干嘛用的呢?由于每次循环过后a数组会右边循环r次,我们用这个get就可以找到a[j]转i次后的位置,那么就是为((x-(i-1)*r)%n+n)%n,至于这个十分显然,自己可以试一试,这里就不讲了,唯一提一下由于((x-(i-1)*r)可能是个负数,%n后还是个负数,那么就要+n再%n
- 最后说一下,我做这道题都是从0开始的,这是因为方便get的使用,然后还有有一些细节我会在代码中再说。
- 30分做法
- 我们知道,对于每次循环的i,与它有关的id只有i与i+1,那么对于f[i][j]同理,与它有关的只有f[i-1][k],所以我们枚举一个k表示i-1时选的id
- 然后由此设出方程f[i][j]=max(f[i-1][k]+a[get(min(j,k))]-a[get(max(j,k))+1]),那么我们是将题目中的两次循环一起做的,加的a时第一次循环的,减的a是第二次循环的(这里是因为a成为相反数,所以也就是用减号),上面的方程自行理解一下先。
- 之后枚举i,j,k就依照上面的方程n3方转移就好了,这样子我们没有输出第二行,所以60分只能拿一半。
- 然后附上30分代码
#include<cstdio>
#include<iostream>
#include<cstring>
#define get(x) ((x-(i-1)*r)%n+n)%n//这里的get我们用宏定义就不需要把i给传进来
#define N 3007
using namespace std;
int n,r,a[N],f[N][N],ans;
int main(){
memset(f,-0x3f3f3f3f,sizeof(f));
memset(f[0],0,sizeof(f[0]));
//关于初始化问题,题目说id一共n+1个,那么我们是从0到n去做的,然后这里是除了f[0]都是无穷小的。然后这个初始化还有下面的n3方与正解是有关联的。
scanf("%d%d",&n,&r);
for(int i=0;i<n;i++)//前面提到这题从0开始
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
for(int j=0;j<=n-2;j++){//id取数最多是n-1,我们就从0到n-2
for(int k=0;k<=n-2;k++)
f[i][j]=max(a[(get(min(j,k)))%n]-a[(get(max(j,k))+1)%n]+f[i-1][k],f[i][j]);
//a[(get(max(j,k))+1)%n]可以发现后面又%n,因为+1后可能变成n,然后这里也可以写成a[(get(max(j,k)+1)],也就是1一起进入get
}
for(int i=0;i<=n-2;i++)
ans=max(ans,f[n][i]);
printf("%d",ans);
}
- 60分做法
- 这个没有打,就提一下,设g[i][j]为上一位选的是什么,然后一旦f[i][j]要转移那么g也一起转移,这个思路用来100分是没有问题的,所以讲一百分时就再细讲
- 50分做法
- . 这个是码过的。那么30分十分显然是n3方过不去,所以我们就要讨论n方解决这个问题
- 首先30分做法里,我们根据题意设出了方程f[i][j]=max(f[i-1][k]+a[get(min(j,k))]-a[get(max(j,k))+1]),我这里还是解释一下吧,我们当前是第i个位置,要选一个数字,然后我们这里的第i个位置要通过i-1来转移,然后如题目,在i时,index只跟i与i+1的id有关,所以第i-1个位置可以去找第i个位置的答案,所以同理,反过来i可以用i-1找。之后说一下这个a,我们现在枚举了j,k,j是目前的i的位置放j,k是i-1的位置放k,显然通过i-1来转移的话,对于题目中的第一个循环,是加上a[min(id[i],id[i+1])],那么这里的id[i]实际上就是k,id[i+1]就是j,然后转移就很显然了,那么对于题目中第二个循环是同理的,由于要求取相反数,所以也就是减去。
- 那么我们就要考虑优化了,看到上面的方程,我们是否可以先考虑一下j,k的大小关系呢,因为确定了它们的大小关系,+a[get(min(j,k))]-a[get(max(j,k))+1])中间的min,max就可以去掉了,可以直接用j或k来代替。
- 分情况讨论
- 若j<=k,方程就应该是f[i-1][k]+a[get(j)]-a[get(k)+1]这样对吧,可以发现中间有两个单项式与j没有关系,那么我们就可以把它(f[i-1][k]-a[get(k)+1])的最大值,用个变量储存起来,之后对于f[i][j]就直接O(1)转移了。
- 若j>=k,其实同理上面的方程就变成了,f[i-1][k]+a[get(k)]-a[get(j)+1] 然后转移同理。
- 最后说一下,对于j<=k,我们是从后往前遍历的,对于j>=k,我们是从前往后遍历的。因为关于k的值是每次遍历时维护的,所以k大的时候就是从后往前,小的时候就是从前往后,然后实际代码中是没有k的,只依靠j来转移
- 100分做法
- 60分与50分合并就好了。
#include<cstdio>
#include<iostream>
#include<cstring>
#define get(x) ((x-(i-1)*r)%n+n)%n
#define N 3007
using namespace std;
int n,r,a[N],f[N][N],g[N][N],ans=-0x3f3f3f3f;
void print(int i,int j){
if(i!=0) print(i-1,g[i][j]);
printf("%d ",j+1);
}
int main(){
memset(f,-0x3f3f3f3f,sizeof(f));
memset(f[0],0,sizeof(f[0]));//初始化与30分做法一样
scanf("%d%d",&n,&r);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
int mx=-0x3f3f3f3f,pos;//mx以及下面的rx是储存关于k的最大值的,pos是上一次选的位置
for(int j=0;j<=n-2;j++){//从前往后遍历,是j>=k的情况
if(mx<f[i-1][j]+a[get(j)]){//最大值更新
mx=f[i-1][j]+a[get(j)];
pos=j;//位置也要换
}
if(f[i][j]<mx-a[(get(j)+1)%n]){//f数组更新
f[i][j]=mx-a[(get(j)+1)%n];
g[i][j]=pos;//记录i-1的位置
}
}
int rx=-0x3f3f3f3f;
for(int j=n-2;j>=0;j--){//与上面同理
if(rx<f[i-1][j]-a[(get(j)+1)%n]){
rx=f[i-1][j]-a[(get(j)+1)%n];
pos=j;
}
if(f[i][j]<rx+a[get(j)]){
f[i][j]=rx+a[get(j)];
g[i][j]=pos;
}
}
}
int np;
for(int i=0;i<=n-2;i++)
if(ans<f[n][i])
ans=f[n][i],np=i;
printf("%d\n",ans);
print(n,np);//递归求方案
}

这篇博客介绍了Warshall-Turing-Fourier变换(WTF)的概念,给出了输入输出的定义,并提供了样例输入输出。博主详细解析了算法过程,指出这是一个动态规划问题,并分享了不同复杂度的解决方案,包括30分、50分和100分的做法。博主还特别强调了如何优化转移方程以减少时间复杂度。
&spm=1001.2101.3001.5002&articleId=107500298&d=1&t=3&u=f83b868223264ffe9bf6dbf38ac5887c)
7397

被折叠的 条评论
为什么被折叠?



