card

problem:

有一个3行4列的棋盘
0 1 2 3
4 5 6 7
8 9 10 11
接着依次给出对手的6张卡牌和己方的6张卡牌,编号为0-5,6-11,双方轮流下牌。
最后给出已经下了的卡牌编号和棋盘位置,如果没有默认己方先手。
如:
0 5
7 3
表示已经下了两张牌,这局对手先开局,对手先把0号牌下在棋盘的5号位,己方将7号牌下在3号位。
每张牌依次给出 北 西 南 东 四个属性值,不超过20。
在一次落子后,进行4次判定,如果这个牌的北方向有牌,且该牌暂时属于对方,且这张牌的 北 属性 严格大于上方牌的 南 属性,那么上方(北方)的牌划归下牌者所有,其余方向依此类推。下一张牌后,最多可以改周围4张的归属。
已经下的牌是固定了的,之后的操作双方均绝顶聪明。
己方的得分是最后的归属牌数-6
请输出己方的最大得分,以及下一张牌会下的位置(自己或对手),要求字典序最小。并输出这张卡牌的四维属性。
示例:

1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1

2 2 2 2
2 2 2 2
2 2 2 2
2 2 2 2
2 2 2 2
2 2 2 2

3 9

输出:

6
6 5
2 2 2 2

solution:

基本的bruteforce是min-max交替搜索。量级在\((12!)*(6!)*(6!)*12\approx 3*10^{15}\)左右。

剪枝一:

有力的剪枝。
考虑一种合法的答案序列,它的最终得分必然是搜索树上所有min节点的最小值,和所有max节点的最大值。所以在向下搜索的时候,由于祖先链上已有的兄弟节点的返回值约束,会要求该节点返回的值要在一个开区间(L, R)内。
以min节点为例,子树返回值>=R continue掉。因为这是个取min的节点,这棵子树相当于没有作用。如果出现了<=L 的子树,说明不合法,直接返回-inf,让上层的max节点忽略它。
如果min节点的所有子树返回值均>=R ,那么返回R即可。
如果某棵子树在合法区间内,那么可以压缩这个合法区间再搜下一颗子树。
(注意由于返回值的不同,即使开区间内没有合法取值,也不能直接return)
max节点的操作同理。

剪枝二:

用时大概优化到原来的70%。
假设当前得分是now_score,以max节点(己方出牌)为例。最好情况是,自己剩余的所有手牌四维属性均大于对方手牌,最差情况是自己全是弱牌。用当前得分+极限情况尝试剪枝。
当前的棋盘归属是一个\(3^{12}\)的地图,每个位置只有0(对手棋子)/1(己方棋子)/-1(未下)三种状态,标记为s。f[s]表示自己全是强牌的时候,能获取的最大得分(在当前分数基础上的increasement)。g[s]表示自己全是弱牌的时候,对方会获取的最小得分(对方的increasement)。f, g数组都是大于等于0的值。
对于一个\(s->t\)的地图转移,显然有\(f[s]=max\{eat(s->t)+g[t]\}, g[s]=min\{f[t]\}\),eat是在选择了一个位置后,吃掉对方的子数。
在搜索的时候,以max节点(己方下棋)为例,它的最终得分被固定在\(now\_score-g(s')\)\(now\_score+f(s)\)之间。其中s是自己视角下的地图,\(s'\)是对手视角的地图(01翻转)。
对于min节点,\(final\_score \in [now\_score - f(s'), now\_score + g(s)]\)
通过判断 可能的最终得分 和 合法取值 的区间交,若无交集可以提前退出子树搜索。
f, g数组需要dp预处理,时间在\(3^{12}*12^2\)左右。

Result:

-O3优化,1000次随机数据测试,最慢为158.366s。

#include <bits/stdc++.h>
using namespace std;
#define P pair<char, char>
#define mp(x, y) make_pair(x, y)

vector< P > oper_list;
const int len = 3;
const int wide = 4;
const int _size = len * wide;
const int _num = _size + (_size&1);
const int half = (_num >> 1);
const int _dir = 4;
const int maxs = 531441;
char f[maxs], g[maxs];
int rev[maxs];
char step_forward[_size][_dir];
char board[_size], sum_board = 0;
char belong[_size], sum_belong = 0;
bool used[_num];
char card[_num][_dir];
int next_k = -1, next_x = -1;
char input_size = 0;
char now_player = 0;

inline void Extend(int s,char *_map) {
    for (int i=0; i<_size; i++) _map[i] = s%3-1, s/=3;
}

inline int Mark(char *_map) {
    int s=0;
    for (char i=_size-1; i>=0; i--) s = (s<<1) + s + (_map[i]+1);
    return s;
}

inline int Rev(char *_map) {
    for (int i=0; i<_size; i++) if (_map[i] != -1) _map[i] = 1 - _map[i];
    return Mark(_map);
}

char Dp(char *p, int s) {
    if (p[s] != -1) return p[s];
    if (p == f) {
        f[s] = 0;
        char _map[_size], new_map[_size];
        Extend(s, _map);
        for (char x=0; x<_size; x++) if (_map[x] == -1) {
            for (int i=0; i<_size; i++) new_map[i] = _map[i];
            new_map[x] = 1;
            char score = 0;
            for (char u=0; u<_dir; u++) {
                char move_to = step_forward[x][u];
                if (move_to == -1 || new_map[move_to] != 0) continue;
                ++score;
                new_map[move_to] = 1;
            }
            int t = Mark(new_map);
            f[s] = max(f[s], (char)(score + Dp(g, t)));
        }
    }
    else {
        g[s] = 100;
        char _map[_size], new_map[_size];
        Extend(s, _map);
        for (char x=0; x<_size; x++) if (_map[x] == -1) {
            for (int i=0; i<_size; i++) new_map[i] = _map[i];
            new_map[x] = 0;
            int t = Mark(new_map);
            g[s] = min(g[s], Dp(f, t));
        }
        if (g[s] == 100) g[s] = 0;
    }
    return p[s];
}

inline void Preprocess() {
    for (int i=0; i<_size; i++){
        if (i < wide) step_forward[i][0] = -1;
        else step_forward[i][0] = i - wide;
        if (i % wide == 0) step_forward[i][1] = -1;
        else step_forward[i][1] = i - 1;
        if (i + wide < _size) step_forward[i][2] = i + wide;
        else step_forward[i][2] = -1;
        if (i % wide == wide - 1) step_forward[i][3] = -1;
        else step_forward[i][3] = i + 1;
    }

    memset(f, -1, sizeof(f));
    memset(g, -1, sizeof(g));
    char tmp_map[_size];
    for (int s=0; s<maxs; s++) {
        Dp(f, s);
        Dp(g, s);
        Extend(s, tmp_map);
        rev[s] = Rev(tmp_map);
    }
}

inline void Simulate(char k, char x) {
    oper_list.push_back( mp(k, x) );
    board[x] = k;
    bool cur = (k >= half? 1: 0);
    sum_board += cur;
    belong[x] = cur;
    sum_belong += cur;
    used[k] = true;
    for (int u=0; u<_dir; u++) if (step_forward[x][u] != -1) {
        char move_to = step_forward[x][u];
        if (board[move_to] != -1) {
            char p = board[move_to];
            char &val = belong[move_to];
            if (val != cur) {
                char v = (u >= 2? u-2: u+2);
                if (card[k][u] > card[p][v]) {
                    sum_belong -= val;
                    val = cur;
                    sum_belong += cur;
                }
            }
        }
    }
}

inline void Initialize() {
    freopen("chess.in", "r", stdin);
    for (int i=0; i<_num; i++)
        for (int j=0; j<_dir; j++) {
            int val = 0;
            cin >> val;
            card[i][j] = val;
        }
    memset(board, -1, sizeof(board));
    memset(belong, -1, sizeof(belong));
    memset(used, false, sizeof(used));
    int k = -1, x = -1;
    cin >> k >> x;
    while (k >= 0) {
        Simulate(k, x);
        k = -1, x = -1;
        cin >> k >> x;
    }
    fclose(stdin);
    input_size = oper_list.size();
}

inline void Reverse(char k, char x, char *tmp_belong, char tmp_sum) {
    oper_list.pop_back();
    board[x] = -1;
    if (k >= half) --sum_board;
    for (int i=0; i<_size; i++) belong[i] = tmp_belong[i];
    sum_belong = tmp_sum;
    used[k] = false;
}

char Dfs(char cur, char min_limit, char max_limit) {
    char now_score = sum_belong - sum_board;
    if (oper_list.size() == _size) return now_score;
    char invalid = (cur? 100: -100);
    int s = Mark(belong);
    int t = rev[s];
    if (cur) {
        if (now_score - g[t] >= max_limit) return invalid;
        if (now_score + f[s] <= min_limit) return min_limit;
    }
    else {
        if (now_score + g[s] <= min_limit) return invalid;
        if (now_score - f[t] >= max_limit) return max_limit;
    }
    char top_score = -invalid;
    char enemy = (cur == 0? half: 0);
    char tmp_sum = sum_belong;
    char tmp_belong[_size];
    for (int i=0; i<_size; i++) tmp_belong[i] = belong[i];
    for (char k=cur; k< cur + half; k++) if (!used[k])
        for (char x=0; x<_size; x++) if (board[x] == -1) {
            Simulate(k, x);
            char score = Dfs(enemy, min_limit, max_limit);
            Reverse(k, x, tmp_belong, tmp_sum);
            if (cur) {
                if (score >= max_limit) return invalid;
                if (score <= min_limit) continue;
                min_limit = score;
                if (score > top_score) {
                    top_score = score;
                    if (oper_list.size() == input_size) next_k = k, next_x = x;
                }
            }
            else {
                if (score <= min_limit) return invalid;
                if (score >= max_limit) continue;
                max_limit = score;
                if (score < top_score) {
                    top_score = score;
                    if (oper_list.size() == input_size) next_k = k, next_x = x;
                }
            }
       }
    if (top_score == -invalid) {
        if (cur) return min_limit;
        else return max_limit;
    }
    return top_score;
}

int main(void) {
    Preprocess();
    Initialize();
    now_player = ((oper_list.size() > 0) && (oper_list.rbegin()->first >= half)? 0: half);
    clock_t start = clock();
    int ans = Dfs(now_player, -100, 100);
    clock_t end = clock();
    //cout << (end - start) / 1000000.0 << endl;
    cout << ans << endl;
    cout << next_k << " " << next_x << endl;
    for (int i=0; i<_dir; i++) cout << (int)card[next_k][i] << " ";
    return 0;
}
posted @ 2025-10-06 21:15  KsCla  阅读(8)  评论(0)    收藏  举报