[NOI 2005] 瑰丽华尔兹:dp+单调队列

状态转移方程,以船体向南倾斜为例,其中 f[i][j][k] 表示经历前k个时间段后停留在第i行第j列的最长滑行距离:

f[i][j][k]=max{f[id][j][k1]+d,0d<}

注意到需要考虑的状态是同一列上的一段区间,我想到用线段树优化转移。每行每列各两棵,维护 f[i][j][k]+i 等,时间复杂度 O(n2k lgn) ,还有大O记号下隐藏的常数。
具有可行性,但是能否做得更好呢?
贺神说它要用单调队列,我咋没用上呢?
确定递推顺序的时候,发现查询的区间有许多重叠,就像——滑动窗口。再者,同样以南为例,按照i递增的顺序递推,如果
f[i][j][k]+if[i][j][k]+i and i>i

那么 f[i][j][k] 不会差于 f[i][j][k] ,后者可以不再考虑。

开始写代码吧。东南西北四种对称情形,抽取它们的内在结构,避免冗长的代码——这一点黄学长做得比我好。截至目前(2016年9月10日晚),我的程序是bzoj上倒数第三慢的,推测主要原因来自于三维数组寻址。

昨天晚上写好了,但是有bug——某处f[i][j][k-1]误作f[i][j][k]。用当年的数据,也就30分了。我本应该发现——之前因为另一个问题输出中间变量,和我预期的不一样。但是由于答案正确,没有深究。注意细节。关注每一处异常。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
using namespace std;
const int MAX_N = 200, MAX_M = 200, MAX_K = 200+1, INF = 1<<30;
int N, M, head, tail, f[MAX_N][MAX_M][MAX_K];
char m[MAX_N][MAX_M];
struct Point {
    int i, j;
    int operator-(const Point& rhs) const
    {
        return abs(i - rhs.i + j - rhs.j);
    }
    int get_f(int k)
    {
        return f[i][j][k];
    }
} q[MAX_N];

void upd(int i, int j, int k, int t)
{
    if (m[i][j] == 'x') {
        tail = head;
        return;
    }
    Point p = (Point){i, j};
    while (head != tail && q[head] - p > t)
        ++head;
    while (head != tail && q[tail-1].get_f(k-1) + (q[tail-1] - p) <= f[i][j][k-1])
        --tail;
    q[tail++] = p;
    f[i][j][k] = q[head].get_f(k-1) + (q[head] - p);    
}

inline void north(int k, int t)
{
    for (int j = 0; j < M; ++j) {
        head = tail = 0;
        for (int i = N-1; i >= 0; --i)
            upd(i, j, k, t);
    }
}

inline void south(int k, int t)
{
    for (int j = 0; j < M; ++j) {
        head = tail = 0;
        for (int i = 0; i < N; ++i)
            upd(i, j, k, t);
    }
}

inline void east(int k, int t)
{
    for (int i = 0; i < N; ++i) {
        head = tail = 0;
        for (int j = 0; j < M; ++j)
            upd(i, j, k, t);
    }
}

inline void west(int k, int t)
{
    for (int i = 0; i < N; ++i) {
        head = tail = 0;
        for (int j = M-1; j >= 0; --j)
            upd(i, j, k, t);
    }
}

template<typename T>
void read(T& x)
{
    x = 0;
    char c = getchar();
    while (!isdigit(c))
        c = getchar();
    while (isdigit(c)) {
        x = x*10 + c - '0';
        c = getchar();
    }
}

int main()
{
    int x, y, K;
    read(N); read(M); read(x); read(y); read(K);
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < M; ++j)
            f[i][j][0] = -INF;
    f[x-1][y-1][0] = 0;
    for (int i = 0; i < N; ++i)
        scanf("%s", m[i]);
    int s, t, d, tm;
    for (int i = 1; i <= K; ++i) {
        read(s); read(t); read(d);
        tm = t-s+1;
        switch (d) {
            case 1:
            north(i, tm); break;
            case 2:
            south(i, tm); break;
            case 3:
            west(i, tm); break;
            case 4:
            east(i, tm); break;
        }
    }
    int ans = -INF;
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < M; ++j)
            ans = max(ans, f[i][j][K]);
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值