首页 > 其他分享 >「解题报告」[CQOI2015]标识设计

「解题报告」[CQOI2015]标识设计

时间:2023-01-18 22:01:28浏览次数:67  
标签:插头 状态 cnt int 31 标识 解题 swap CQOI2015

题目传送门

首先看到题很容易能想到插头 DP。但是数据范围很大,\(n,m \le 30\),直接压会很爆炸。不过发现合法状态很少,因为轮廓线上最多同时只能有三个插头,所以大部分状态都是不合法的。

一种方法是直接哈希表记录合法状态,可以参考其他题解,另一种方法是直接记录这三个插头的位置,而不是记录每个位置有没有插头。

发现 \(30\) 这个数正好能压到 \(32\) 中,所以我们可以这样压缩状态:前两位代表已经放了几个插头(\(3=2^2-1\)),后面有三个五位记录三个插头的位置(\(31=2^5-1\)),这样一个状态最大只有 \(2^{17}-1=131071\)。

转移就比较轻松了,分五种情况:

  1. 这一格是障碍:直接转移过去。
  2. 没有向右和向下的插头:这一格可以不放,可以为一个 L 的起点。
  3. 只有向下的插头:那么说明这肯定是 L 上面转移下来的,可以继续向下延伸,或者向右拐。
  4. 只有向右的插头:那么说明一定是 L 拐弯之后过来的,可以继续向右延伸,或者在这里结束。
  5. 同时有向右和向下的插头:此种情况不合法,直接跳过。

这样实现略麻烦,我使用了一个结构体来封装状态,自认为还是挺清新的)这样就可以比较方便的转移,具体看代码吧。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
struct DATA {
    int cnt, a, b, c;
    DATA(int q) { // 将 int 转换为状态
        cnt = q & 3;
        a = q >> 2 & 31;
        b = q >> 7 & 31;
        c = q >> 12 & 31;
    }
    DATA(int cnt, int a, int b, int c) : cnt(cnt), a(a), b(b), c(c) {}
    operator int() { // 将状态转换为 int
        if (a > b) swap(a, b);
        if (b > c) swap(b, c);
        if (a > b) swap(a, b);
        return cnt | (a << 2) | (b << 7) | (c << 12);
    }
    DATA replace(int a, int b, int d = 0) { // 替换插头的位置,d 代表 cnt 是否加 1
        DATA c = *this;
        if (c.a == a) c.a = b;
        else if (c.b == a) c.b = b;
        else if (c.c == a) c.c = b;
        c.cnt += d;
        return c;
    }
};
int n, m;
const int T = 10007;
struct Hash { // 哈希表
    int fst[T], nxt[T], tot, v[T], k[T];
    void insert(int d, int vv) {
        int h = d % T;
        v[++tot] = vv;
        k[tot] = d;
        nxt[tot] = fst[h];
        fst[h] = tot;
    }
    int find(int d) {
        int h = d % T;
        int p = fst[h];
        while (p && k[p] != d) p = nxt[p];
        return v[p];
    }
    void clear() {
        tot = 0;
        memset(fst, 0, sizeof fst);
    }
}mp[2];
long long f[2][MAXN], cnt;
void S(int d, int a, long long b) { // 更新答案函数
    int q = mp[d].find(a);
    if (q) f[d][q] += b;
    else mp[d].insert(a, ++cnt), f[d][cnt] = b;
}
char ch[44][44];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%s", ch[i] + 1);
    f[0][1] = 1, mp[0].insert(DATA{0, 0, 0, 0}, 1);
    for (int i = 1, t = 0; i <= n; i++) {
        for (int j = 1; j <= mp[t].tot; j++) { // 新一行状态要向右平移
            DATA ori = mp[t].k[j]; 
            if (ori.a) ori.a++; 
            if (ori.b) ori.b++; 
            if (ori.c) ori.c++;
            mp[t].k[j] = ori;
        }
        for (int j = 1; j <= m; j++) {
            t ^= 1, cnt = 0;
            mp[t].clear();
            for (int k = 1; k <= mp[t ^ 1].tot; k++) {
                DATA s = mp[t ^ 1].k[k];
                long long ans = f[t ^ 1][mp[t ^ 1].v[k]];
                int r = s.a == j || s.b == j || s.c == j, 
                    d = s.a == j + 1 || s.b == j + 1 || s.c == j + 1;
                if (ch[i][j] == '#') { // case 1
                    S(t, s, ans);
                } else if (!r && !d) { // case 2
                    S(t, s, ans);
                    if (ch[i + 1][j] == '.' && s.cnt < 3)
                        S(t, s.replace(0, j, 1), ans);
                } else if (!r && d) { // case 3
                    if (ch[i + 1][j] == '.') S(t, s.replace(j + 1, j), ans);
                    if (ch[i][j + 1] == '.') S(t, s, ans);
                } else if (r && !d) { // case 4
                    if (ch[i][j + 1] == '.') S(t, s.replace(j, j + 1), ans);
                    S(t, s.replace(j, 0), ans);
                } else if (r && d) { // case 5
                    "go to hell";
                }
            }
        }
        if (i == n) {
            printf("%lld\n", f[t][mp[t].find(DATA{3, 0, 0, 0})]);
        }
    }
    return 0;
}

标签:插头,状态,cnt,int,31,标识,解题,swap,CQOI2015
From: https://www.cnblogs.com/apjifengc/p/17060688.html

相关文章

  • 「解题报告」ARC142D Deterministic Placing
    好?首先我们可以发现,第一步和第三步的局面必须相等,因为第二步可以反着走回第一步,如果不相等那么下一步走的方案就不唯一了。然后我们考虑走的形式,由于是一棵树,没有环,我们......
  • 「解题报告」[ARC143E] Reversi
    挺弱智的题但是我不会首先从一个叶子开始考虑,如果这个叶子是白的那么就应该先选它,否则就应该先选它父亲。然后这样我们可以递归着对子树进行计算,一些子树需要先选,一些子......
  • AtCoder Beginner Contest 285 解题报告
    AtCoderBeginnerContest285解题报告\(\text{DaiRuiChen007}\)ContestLinkA.EdgeChecker2假设\(a\geb\),当且仅当\(\left\lfloor\dfraca2\right\rfloor=b\)......
  • Vulnhub之Dobby详细解题过程(不同的获得wordpress后台密码方法)
    Dobby作者:jason_huawen靶机信息名称:Hogwarts:Dobby地址:识别目标主机IP地址─(kali㉿kali)-[~/Vulnhub/Dobby]└─$sudonetdiscover-ieth1-r192.168.56.0/2......
  • java 巧用标识符
    很多时候,巧用标识会很大的减少代码量和厘清代码逻辑;比如下面,这里的entName和code都有可能为空,也可能都不为空,但是当两entName都不为空且相等,或者当两code都不为空且相等时,才......
  • Java基础02 关键字与标识符
    关键字与标识符关键字随着不断深入学习Java逐渐理解和掌握标识符定义Java中所有的组成部分都需要名字,类名,变量名,各种方法名都称为标识符命名首字母:a-z;A-......
  • Codeforces Round #843 (Div. 2)解题报告
    第一篇文章\ovo/AProblem:把只含有ab的字符串分成三份,使中间的字符串字典序是并列最大或最小,求一种方案Solution:假设前两个字符相同,让前两个字符串长度均为1,这两个字符......
  • 标识符
    标识符可以标识什么?可以标识类名,方法名,变量名,接口名,常量名.....程序员有权利命名的都是标识符标识符的命名规则命名规则属于语法机制,必须遵守,不遵守命名规则标识......
  • Mysql主从同步成功的标识
    ......
  • Java的标识符与编码规范
    一、Java标识符1.代码回顾在认识什么是Java里的标识符之前,咱们还是先把上节课中的那段代码拿过来复习一下,如下:publicclassHelloWorld{publicstaticvoidmain(Str......