P7959 [COCI2014-2015#6] WTF 题解
Posted jeefy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P7959 [COCI2014-2015#6] WTF 题解相关的知识,希望对你有一定的参考价值。
P7959 [COCI2014-2015#6] WTF 题解
呃,是一道
DP
题
说实话,原题实际上是不要输出一种方法的……但是似乎放这道题的人想增加一点难度?
这里有两种做法,但都是 DP
。
预备观察
我们首先观察一些性质,以方便解题。
-
循环不变:我们可以观察到,在 \\(n\\) 次变换后,序列会还原。也就是说,两个循环在同一个 \\(i\\) 上操作的序列是一样的。
-
下标大小:其实可以看到,下标是一大一小,也就是 \\(\\min(ID_i, \\mathitID_i+1)\\) 和 \\(\\max(ID_i, ID_i + 1)+1\\)。意味着我们在 \\(ID_i\\) 的选择关于,且仅关于 \\(ID_i - 1\\) 的选择。所以考虑
DP
转移。 -
连续性:不难发现,其实选择是这么一些边:\\((ID_i, ID_i + 1)\\) 和 \\((ID_i + 1, ID_i + 2)\\),也就是说每一个状态是相关联的。
接下来就可以开始正式解题了。
感觉上面讲的都是废话
解法1:强行DP
这也是我拿到这一道题的第一想法……也是正解的一种吧
在观察出来下标大小的关系之后,其实就可以设一个 \\(DP\\) 了。
令 \\(f_i,j\\) 表示在 \\(ID_i + 1\\) 选 \\(j\\) 所能取到的最大值。
于是可以有这么一个转移方程:
\\(k\\) 上界为 \\(n - 1\\),这是题面中要求了的。
包括 \\(j\\) 其实也 \\(\\in [1, n-1]\\)
所以就有一个 \\(O(n^3)\\) 的写法了。
但是很明显,必须优化到 \\(O(n^2)\\) 才能过。
我们把 \\(\\min \\max\\) 拆开:
其实内部关于 \\(j\\) 的边界并没有那么重要
很明显,后面两个部分可以通过前后缀 \\(\\max\\) 搞定。于是我们可以在 \\(O(1)\\) 内转移。
总时间复杂度成功变为 \\(O(n^2)\\)。
不过还要注意一个点,每一次转移的时候,需要手动模拟一次 \\(Rotate(n, r)\\)。
那么核心代码如下:
pre[0] = suf[n] = -1e9;
for (i = 1; i <= n; ++i, rotate())
// prefix k
for (k = 1; k < n; ++k)
// pre[k] = max(pre[k - 1], f[i - 1][k] + A[k]);
if (pre[k - 1] >= f[i - 1][k] + A[k])
pre[k] = pre[k - 1];
pref[k] = pref[k - 1];
else
pre[k] = f[i - 1][k] + A[k];
pref[k] = k;
// suffix k
for (k = n - 1; k; --k)
// suf[k] = max(suf[k + 1], f[i - 1][k] - A[k + 1]);
if (suf[k + 1] >= f[i - 1][k] - A[k + 1])
suf[k] = suf[k + 1];
suff[k] = suff[k + 1];
else
suf[k] = f[i - 1][k] - A[k + 1];
suff[k] = k;
for (j = 1; j < n; ++j) // enum cur ID[i + 1]
int p = pre[j] - A[j + 1], s = suf[j] + A[j];
if (p >= s)
f[i][j] = p;
trans[i][j] = pref[j];
else
f[i][j] = s;
trans[i][j] = suff[j];
最后通过 trans
数组输出方案即可。
不过说实话,这个空间复杂度确实不够优秀。
做法2:std做法
其实可以发现,对于每一个 \\(i\\),设
于是有 \\(sum += A_id_1 -A_id_2 +1\\)。
这似乎提醒这我们做一个前缀差分。
于是我们设 \\(b_i = A_i + 1 - A_i\\)。
所以可以得到 \\(A_id_2 + 1 - A_id_1 = \\sum_i = id_1^id_2 b_i\\)。
原本我们需要最大化,那么此时,我们需要最小化 \\(A_id_2 + 1 - A_id_1\\)。
不过,如果我们把初始的 \\(A\\) 序列全部取反,那么我们还是需要最大化上面这个式子。
贴出的代码中也做了如上操作。
注意加减顺序。以及 \\(b\\) 只有 \\(n-1\\) 个元素。
于是我们可以构建出一个 \\((n-1) \\times n\\) 的矩阵 \\(B\\),其中每一行是对应旋转后的 \\(A\\) 的差分序列。
我们在寻找 \\(sum\\) 的过程,其实就是把所有路径上的 \\(b\\) 加起来,于是,问题转化为寻找在 \\(B\\) 上的一条最短路径。
不过,由于我们只能向下,或者左右走,并且不能重复走,所以也考虑 \\(DP\\)。
设 \\(f_i, j, k\\) 表示,走到 \\((i, j)\\) 这个位置,来的方向是 \\(k\\) ,的最长路径。
\\(k \\in [0, 3)\\),分别表示从上面转移,从右侧转移,从左侧转移。
或者可以说是向下走,向右走,向左走转移(代码中的意义)。
于是有如下方程:
记录一下转移来的路径,在拐点的地方输出即可。
为了偷懒,就直接贴出不记录路径的代码了。
总时间复杂度 \\(O(n^2)\\):
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3003, MINUS_INF = -1e9;
int a[N][N];
int b[N][N];
int dp[N][N][3];
#define DOWN 0
#define LEFT 1
#define RIGHT 2
// 三个方向选其优
int best(int i, int j)
return max(dp[i][j][DOWN],
max(dp[i][j][LEFT], dp[i][j][RIGHT]));
int main ()
int n, r;
cin >> n >> r;
// 注意整个程序的下标是从 0 开始
// 也就是 [0, n) 而非 [1, n]
for (int i = 0; i < n; ++i)
cin >> a[0][i];
a[0][i] *= -1;
int position = i;
// 构建旋转后的序列
for (int j = 1; j < n; ++j)
position = (position + r) % n;
a[j][position] = a[0][i];
// 初始化dp表
for (int i = 0; i < n; ++i)
for (int j = 0; j < n - 1; ++j)
// 构建差分序列
b[i][j] = a[i][j + 1] - a[i][j];
for (int k = 0; k < 3; ++k)
dp[i][j][k] = MINUS_INF;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n - 1; ++j)
// 处理从上一行的转移
dp[i][j][DOWN] = b[i][j] + (i > 0 ? best(i - 1, j) : 0);
// 处理从左边转移
if (j > 0)
dp[i][j][RIGHT] = b[i][j] +
max(dp[i][j - 1][DOWN], dp[i][j - 1][RIGHT]);
// 反着来一次从右边的转移
for (int j = n - 3; j >= 0; --j)
dp[i][j][LEFT] = b[i][j] +
max(dp[i][j + 1][DOWN], dp[i][j + 1][LEFT]);
// 输出最终的答案
int sol = MINUS_INF;
for (int j = 0; j < n - 1; ++j)
sol = max(sol, best(n - 1, j));
cout << sol << endl;
[COCI2006-2007#1] Slikar
【timegate】
https://www.luogu.org/problem/P4328
【解题思路】
广搜详见注释
【code】
1 #include<cstdio> 2 #include<cstring> 3 #include<queue> 4 #define N 60 5 using namespace std; 6 struct Queue 7 8 int x,y; 9 ; 10 struct Way 11 12 int x,y,time; 13 ; 14 queue<Queue>q;//洪水蔓延的队列 15 queue<Way>b;//刺猬回家的队列 16 int n,m,time[N][N],dx[]=0,1,-1,0,dy[]=1,0,0,-1; 17 bool vis[N][N]; 18 char map[N][N]; 19 int main() 20 21 memset(time,0x3f3f3f,sizeof time);//初值无限大,否则60分 22 scanf("%d%d",&n,&m); 23 for (int i=1;i<=n;i++) 24 25 scanf("%s",map[i]+1);//独特的读入方式 26 for (int j=1;j<=m;j++) 27 28 if (map[i][j]==‘*‘)//立刻入队 29 30 Queue t=(Queue)i,j; 31 q.push(t); 32 time[i][j]=0; 33 vis[i][j]=1; 34 35 if (map[i][j]==‘S‘)//立刻入队 36 37 Way t=(Way)i,j,0; 38 b.push(t); 39 40 41 42 for (;!q.empty();) 43 44 Queue T=q.front(); 45 q.pop(); 46 for (int i=0;i<4;i++) 47 48 int x=T.x+dx[i],y=T.y+dy[i]; 49 if (x>0&&x<=n&&y>0&&y<=m&&!vis[x][y]&&map[x][y]!=‘X‘&&map[x][y]!=‘D‘)//判断下一个点是否合法 50 //入队 51 Queue t=(Queue)x,y; 52 q.push(t); 53 time[x][y]=time[T.x][T.y]+1; 54 vis[x][y]=1; 55 56 57 58 memset(vis,0,sizeof vis);//别忘了更新 59 for (;!b.empty();) 60 61 Way T=b.front(); 62 b.pop(); 63 for (int i=0;i<4;i++) 64 65 int x=T.x+dx[i],y=T.y+dy[i],z=T.time; 66 if (x>0&&x<=n&&y>0&&y<=m&&map[x][y]!=‘X‘&&z+1<time[x][y]&&!vis[x][y])//判断下一个点是否合法 67 //入队 68 Way t=(Way)x,y,z+1; 69 b.push(t); 70 vis[x][y]=1; 71 if (map[x][y]==‘D‘)//一旦找到,就一定是最优的,立刻输出,结束程序 72 73 printf("%d",z+1); 74 return 0; 75 76 77 78 79 puts("KAKTUS"); 80 return 0; 81
以上是关于P7959 [COCI2014-2015#6] WTF 题解的主要内容,如果未能解决你的问题,请参考以下文章
题解 P6467 [COCI2008-2009#6] BUKA