Loj#6434「PKUSC2018」主斗地(搜索)

题面

Loj

题解

细节比较多的搜索题。

首先现将牌型暴力枚举出来,大概是\(3^{16}\)吧。

然后再看能打什么,简化后无非就三种决策:单牌,\(3+x\)和\(4+x\)。

枚举网友打了几张\(3\)和\(4\),然后再枚举吉老师(\(\mathbf {orz}\))打了几张\(3\)和\(4\)。

接着枚举\(3\)搭配了几个\(2\),然后贪心地从大到小去除吉老师手中大小为\(2\)的对子,从小到大去除网友手中大小为\(2\)的对子。之后就是检查单牌是否合法了。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using std::min; using std::max;
using std::swap; using std::sort;
typedef long long ll;

template<typename T>
void read(T &x) {
    int flag = 1; x = 0; char ch = getchar();
    while(ch < '0' || ch > '9') { if(ch == '-') flag = -flag; ch = getchar(); }
    while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); x *= flag;
}

const int _ = 20;
char s[_];
int ans, cnt[_], a[_], orz[_];
int wy[_], jtkl[_], thr[_], fou[_], W[_], J[_], P[_];

int code (char c){
    switch(c) {
    case 'T': return 7;
    case 'J': return 8;
    case 'Q': return 9;
    case 'K': return 10;
    case 'A': return 11;
    case '2': return 12;
    case 'w': return 13;
    case 'W': return 14;
    default: return c - '4' + 1;
    }
}

bool check(int f, int t) {
    for(int i = 0; i <= t; ++i) {
        memcpy(W, wy, sizeof wy), memcpy(J, jtkl, sizeof jtkl);
        if(2 * i + t - i + f * 2  + f * 4 + t * 3 > 17) break;
        int cnt = 0;
        for(int j = 1; j <= 14; ++j) {
            if(W[j] >= 2 && cnt < i) W[j] -= 2, ++cnt;
            if(W[j] >= 2 && cnt < i) W[j] -= 2, ++cnt;
            if(cnt == i) break;
        }
        if(cnt < i) break; cnt = 0;
        for(int j = 14; j >= 1; --j) {
            if(J[j] >= 2 && cnt < i) J[j] -= 2, ++cnt;
            if(J[j] >= 2 && cnt < i) J[j] -= 2, ++cnt;
            if(cnt == i) break;
        }
        if(cnt < i) break;
        memset(P, 0, sizeof P);
        cnt = 2 * f + t - i;
        for(int j = 14; j >= 1; --j) {
            int t = min(cnt, J[j]);
            J[j] -= t, cnt -= t;
            if(!cnt) break;
        }
        if(cnt) continue;
        cnt = 2 * f + t - i;
        for(int j = 1; j <= 14; ++j) {
            int t = min(cnt, W[j]);
            W[j] -= t, cnt -= t;
            if(!cnt) break;
        }
        if(J[14]) continue;
        for(int j = 1; j <= 14; ++j)
            P[j] += W[j], P[j + 1] -= J[j];
        cnt = 0;
        for(int j = 1; j <= 14; ++j) {
            cnt += P[j];
            if(cnt > 0) break;
        }
        if(!cnt) return true;
    } return false;
}

bool check_jtkl(int now, int four, int three, int f, int t, int q1, int q2) {
    if(four == f && three == t) return check(f, t);
    if(now >= 12) return false;
    q1 += thr[now], q2 += fou[now];
    if(q1 > 0 || q2 > 0) return false;
    if(jtkl[now] >= 3) {
        jtkl[now] -= 3;
        if(check_jtkl(now + 1, four, three, f, t + 1, q1 - 1, q2)) return true;
        jtkl[now] += 3;
    }
    if(jtkl[now] >= 4) {
        jtkl[now] -= 4;
        if(check_jtkl(now + 1, four, three, f + 1, t, q1, q2 - 1)) return true;
        jtkl[now] += 4;
    }
    return check_jtkl(now + 1, four, three, f, t, q1, q2);
}

bool check_wy (int now, int four, int three) {
    if(four * 6 + three * 4 > 17) return false;
    if(now > 12) return check_jtkl(1, four, three, 0, 0, 0, 0);
    if(wy[now] >= 3) {
        wy[now] -= 3, ++thr[now];
        if(check_wy(now + 1, four, three + 1)) return true;
        wy[now] += 3, --thr[now];
    }
    if(wy[now] >= 4) {
        wy[now] -= 4, ++fou[now];
        if(check_wy(now + 1, four + 1, three)) return true;
        wy[now] += 4, --fou[now];
    }
    return check_wy(now + 1, four, three);
}

void dfs(int now, int rest) {
    if(!rest) {
        memset(thr, 0, sizeof thr);
        memset(fou, 0, sizeof fou);
        memcpy(wy, a, sizeof a);
        memcpy(jtkl, orz, sizeof orz);
        if(check_wy(2, 0, 0)) ++ans;
        return ;
    }
    if(now > 14) return ;
    for(int i = 0; i <= cnt[now]; ++i) {
        if(i > rest) break;
        orz[now] = i, dfs(now + 1, rest - i), orz[now] = 0;
    }
}

int main () {
    while(scanf("%s", s + 1) != EOF) {
        memset(a, 0, sizeof a);
        for(int i = 1; i <= 12; ++i) cnt[i] = 4;
        cnt[13] = cnt[14] = 1, ans = 0;
        for(int i = 1; i <= 17; ++i)
            ++a[code(s[i])], --cnt[code(s[i])];
        dfs(1, 17), printf("%d\n", ans);
    }
    return 0;
} 

原文地址:https://www.cnblogs.com/water-mi/p/10289540.html

时间: 2024-08-29 11:33:14

Loj#6434「PKUSC2018」主斗地(搜索)的相关文章

loj 6434「PKUSC2018」主斗地

loj 最可做的斗地主系列题(?) 显然的想法是爆搜可怜的牌,然后接着找是否有合法出牌方案.因为总的方案数只有几百万种,所以可以直接枚举每种方案 然后是优化check过程.首先可以发现对子三张牌顺子连对三顺可以拆成若干单牌,飞机可以拆成若干三带一或三带二,所以只有我们只用考虑单牌,三带一,三带二,四带二.如果只考虑单牌,那么一定是两者的牌分别排好序后,可怜某张牌要严格小于网友的对应位置的牌,所以这个可以从大到小枚举牌大小,然后看可怜的每种牌是否都有网友的更大的牌可以配上对,复杂度为\(O(14)

Loj#6432「PKUSC2018」真实排名(二分查找+组合数)

题面 Loj 题解 普通的暴力是直接枚举改或者不改,最后在判断最后对哪些点有贡献. 而这种方法是很难优化的.所以考虑在排序之后线性处理.首先先假设没有重复的元素 struct Node { int poi, id; } a[N]; bool operator < (const Node &a, const Node &b) { return a.poi < b.poi; } bool operator < (const Node &a, const int &am

Loj 6433. 「PKUSC2018」最大前缀和 (状压dp)

题面 Loj 题解 感觉挺难的啊- 状压\(dp\) 首先,有一个性质 对于一个序列的最大前缀和\(\sum_{i=1}^{p} A[i]\) 显然对于每个\(\sum_{i=p+1}^{x}A[i](p+1 \leq x \leq n)<0\) 我们可以以\(p\)分成两个集合 \(n\leq 20\) 所以状压一下 \(sum[i]\)表示当前状态表示的和 \(f[i]\)表示用当前状态的数,组成最大前缀和为\(sum[i]\)的方案数 \(g[i]\)表示当前状态的数,组成的序列,每个前缀

loj#2552. 「CTSC2018」假面

题目链接 loj#2552. 「CTSC2018」假面 题解 本题严谨的证明了我菜的本质 对于砍人的操作好做找龙哥就好了,blood很少,每次暴力维护一下 对于操作1 设\(a_i\)为第i个人存活的概率,\(d_i\)为死掉的概率,\(g_{i,j}\)是除i以外活了j个人的概率 那个选中i人的答案就是 \[a_i\times\sum_{j = 0} ^{k - 1}\frac{g_{i,j}}{j + 1}\] 对于\(g_{i,j}\) ,设\(f_{i,j}\)表示前\(i\)个人有\(

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

目录 题目链接 题解 代码 题目链接 loj#2076. 「JSOI2016」炸弹攻击 题解 模拟退火 退火时,由于答案比较小,但是温度比较高 所以在算exp时最好把相差的点数乘以一个常数让选取更差的的概率降低 代码 #include<ctime> #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define gc getchar() #define

Loj #2541「PKUWC2018」猎人杀

Loj #2541. 「PKUWC2018」猎人杀 题目链接 好巧妙的题! 游戏过程中,概率的分母一直在变化,所以就非常的不可做. 所以我们将问题转化一下:我们可以重复选择相同的猎人,只不过在一个猎人被选择了过后我们就给他打上标记,再次选择他的时候就无效.这样与原问题是等价的. 证明: 设\(sum=\sum_iw_i,kill=\sum_{i被杀死了}w_i\). 攻击到未被杀死的猎人\(i\)的概率为\(P\). 则根据题意\(P=\frac{w_i}{sum-kill}\). 问题转化后:

Loj #2542. 「PKUWC2018」随机游走

Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次询问给定一个集合 \(S\),求如果从 \(x\) 出发一直随机游走,直到点集 \(S\) 中所有点都至少经过一次的话,期望游走几步. 特别地,点 \(x\)(即起点)视为一开始就被经过了一次. 答案对 $998244353 $ 取模. 输入格式 第一行三个正整数 \(n,Q,x\). 接下来 \(

Loj #2192. 「SHOI2014」概率充电器

Loj #2192. 「SHOI2014」概率充电器 题目描述 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品--概率充电器: 「采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决定!SHOI 概率充电器,您生 活不可或缺的必需品!能充上电吗?现在就试试看吧!」 SHOI 概率充电器由 \(n-1\) 条导线连通了 \(n\) 个充电元件.进行充电时,每条导线是否可以导电以 概率决定,每一个充电元件自身是否直接进行充电也由概率决定.随后电能可以从直接充电的元件经

Loj #3111. 「SDOI2019」染色

Loj #3111. 「SDOI2019」染色 题目描述 给定 \(2 \times n\) 的格点图.其中一些结点有着已知的颜色,其余的结点还没有被染色.一个合法的染色方案不允许相邻结点有相同的染色. 现在一共有 \(c\) 种不同的颜色,依次记为 \(1\) 到 \(c\).请问有多少对未染色结点的合法染色方案? 输入格式 第一行有两个整数 \(n\) 和 \(c\),分别描述了格点图的大小和总的颜色个数. 之后两行,每行有 \(n\) 个整数:如果是 \(0\) 则表示对应结点未被染色,否