Acdream 1076 XXX的机器人(dp + 线段树)

题目链接:http://acdream.info/problem?pid=1076

这题DP的状态很好设计,dp[i][j]表示指令i的时候,全排列状态是j,全排列一共就120个,预处理出来就可以了

那么问题就在于对于一个指令怎么快速获得这个整个区间的置换乘积,这步其实利用一个线段树维护就可以了,但是要注意置换是不满足交换律的,所以正序逆序都要保存一遍

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n, m;
int to[55555], num[125][5];
int A[5];

void init() {
    for (int i = 0; i < 5; i++) A[i] = i + 1;
    int cnt = 0;
    do {
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            sum = sum * 10 + A[i];
            num[cnt][i] = A[i];
        }
        to[sum] = cnt++;
    } while (next_permutation(A, A + 5));
}

const int N = 100005;

struct Node {
    int l, r, s[2];
} node[N * 4];

#define lson(x) ((x<<1)+1)
#define rson(x) ((x<<1)+2)

int gao(int a, int b) {
    int sum = 0;
    for (int i = 0; i < 5; i++) {
        A[i] = num[b][num[a][i] - 1];
        sum = sum * 10 + A[i];
    }
    return to[sum];
}

void pushup(int x) {
    node[x].s[0] = gao(node[lson(x)].s[0], node[rson(x)].s[0]);
    node[x].s[1] = gao(node[rson(x)].s[1], node[lson(x)].s[1]);
}

void build(int l, int r, int x = 0) {
    node[x].l = l; node[x].r = r;
    if (l == r) {
        int sum = 0, tmp;
        for (int i = 0; i < 5; i++) {
            scanf("%d", &tmp);
            sum = sum * 10 + tmp;
        }
        node[x].s[0] = node[x].s[1] = to[sum];
        return;
    }
    int mid = (l + r) / 2;
    build(l, mid, lson(x));
    build(mid + 1, r, rson(x));
    pushup(x);
}

int get(int l, int r, int tp, int x = 0) {
    if (node[x].l >= l && node[x].r <= r) return node[x].s[tp];
    int mid = (node[x].l + node[x].r) / 2;
    if (l <= mid && r > mid) {
        if (tp == 0) return gao(get(l, r, tp, lson(x)), get(l, r, tp, rson(x)));
        else return gao(get(l, r, tp, rson(x)), get(l, r, tp, lson(x)));
    }
    else if (l <= mid) return get(l, r, tp, lson(x));
    else if (r > mid) return get(l, r, tp, rson(x));
}

const int INF = 0x3f3f3f3f;

int S[105], T[105], f[105];
int dp[105][125];

int main() {
    init();
    while (~scanf("%d%d", &n, &m)) {
        build(1, n);
        for (int i = 0; i <= m; i++)
            for (int j = 0; j < 120; j++) dp[i][j] = INF;
        for (int i = 1; i <= m; i++) {
            scanf("%d%d", &S[i], &T[i]);
            f[i] = 0;
            if (S[i] > T[i]) {
                swap(S[i], T[i]);
                f[i] = 1;
            }

        }
        int sum = 0, tmp;
        for (int i = 0; i < 5; i++) {
            scanf("%d", &tmp);
            sum = sum * 10 + tmp;
        }
        dp[0][to[sum]] = 0;
       for (int i = 1; i <= m; i++) {
            int sb = get(S[i], T[i], f[i]);
            for (int j = 0; j < 120; j++) {
                if (dp[i - 1][j] == INF) continue;
                int nxt = gao(j, sb);
                dp[i][j] = min(dp[i][j], dp[i - 1][j]);
                dp[i][nxt] = min(dp[i][nxt], dp[i - 1][j] + max(S[i] - T[i], T[i] - S[i]) + 1);
            }
        }
        if (dp[m][to[12345]] == INF) printf("-1\n");
        else printf("%d\n", dp[m][to[12345]]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值