【bzoj1499】[NOI2005]瑰丽华尔兹 【单调队列优化dp】
题目链接
题解:
首先,我们很容易得出dp方程。让表示在第k段时间里走到这个位置的最长滑动距离。这里以向北滑动举例,设第段时间内最多能走步,。则有。于是我们就可以用一个单调队列维护的最大值。
其他四个方向同理,就是循环的顺序变一下而已。
k这一维内存可以用滚动数组优化掉。注意把到不了的位置赋值为-inf,只有队头的最优值不为-inf才更新当前的位置。遇到障碍物,要把队列清空,因为之前的决策全部没用了,走不到当前位置。初始化时把除初始位置之外的都赋值为-inf。
细节详见代码。
#include<cstdio>
const int N=205,inf=0x3f3f3f3f;
int n,m,x,y,k,s,t,d,ans,head,tail,q[N],f[N][N],g[N][N];
char str[N][N];
void dp(int l){
if(d==1){
for(int j=1;j<=m;j++){
head=1,tail=0;
for(int i=n;i>=1;i--){
if(str[i][j]=='x'){
head=1,tail=0;
continue;
}
while(head<=tail&&q[head]-i>l){
head++;
}
while(head<=tail&&g[i][j]+i>g[q[tail]][j]+q[tail]){
tail--;
}
q[++tail]=i;
if(g[q[head]][j]!=-inf){
f[i][j]=g[q[head]][j]+q[head]-i;
}
}
}
}else if(d==2){
for(int j=1;j<=m;j++){
head=1,tail=0;
for(int i=1;i<=n;i++){
if(str[i][j]=='x'){
head=1,tail=0;
continue;
}
while(head<=tail&&i-q[head]>l){
head++;
}
while(head<=tail&&g[i][j]-i>g[q[tail]][j]-q[tail]){
tail--;
}
q[++tail]=i;
if(g[q[head]][j]!=-inf){
f[i][j]=g[q[head]][j]+i-q[head];
}
}
}
}else if(d==3){
for(int i=1;i<=n;i++){
head=1,tail=0;
for(int j=m;j>=1;j--){
if(str[i][j]=='x'){
head=1,tail=0;
continue;
}
while(head<=tail&&q[head]-j>l){
head++;
}
while(head<=tail&&g[i][j]+j>g[i][q[tail]]+q[tail]){
tail--;
}
q[++tail]=j;
if(g[i][q[head]]!=-inf){
f[i][j]=g[i][q[head]]+q[head]-j;
}
}
}
}else{
for(int i=1;i<=n;i++){
head=1,tail=0;
for(int j=1;j<=m;j++){
if(str[i][j]=='x'){
head=1,tail=0;
continue;
}
while(head<=tail&&j-q[head]>l){
head++;
}
while(head<=tail&&g[i][j]-j>g[i][q[tail]]-q[tail]){
tail--;
}
q[++tail]=j;
if(g[i][q[head]]!=-inf){
f[i][j]=g[i][q[head]]+j-q[head];
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
g[i][j]=f[i][j];
if(f[i][j]>ans){
ans=f[i][j];
}
}
}
}
int main(){
scanf("%d%d%d%d%d",&n,&m,&x,&y,&k);
for(int i=1;i<=n;i++){
scanf("%s",str[i]+1);
for(int j=1;j<=m;j++){
f[i][j]=g[i][j]=-inf;
}
}
f[x][y]=g[x][y]=0;
for(int i=1;i<=k;i++){
scanf("%d%d%d",&s,&t,&d);
dp(t-s+1);
}
printf("%d\n",ans);
return 0;
}