每日好题:原来你也玩三国杀(DP动态规划)
I - 原来你也玩三国杀
Description
小 Q 最近听说 “很多” acmer 都爱上了一款游戏《三国杀》。因为小 Q 是一个初学者,所以想自己先偷偷学习一下,然后惊艳所有人。但又因为小 Q 不屑于使用一般的武将,因为他觉得唯有操作型武将才能显得自己的实力,所以他决定使用操作型武将”大宝”(界徐盛)。
你作为小Q的好盆友,告诉他这个不够秀,并向他推荐了教授(沮授)。其中的一个技能为
- 渐营(技能):每当你使用和你上一张使用的牌花色相同时,你可以摸一张牌(第一张牌没有上一张)。
但是这个技能摸牌的随机性太大的,很难操作起来,所以小 Q 选择开一点点挂。使得每次触发技能时,小 Q 能够摸到与自己上一张花色相同的牌。
那么如果一直出相同花色的牌就可以一直摸,无穷无尽,很容易让人发现开挂。所以小 Q 就会故意出与上一张不同的花色的牌,导致技能触发不了,这样就能保证可以牌可以出完。但是又不能乱出,因为乱出显不出自己的实力。
假设初始情况下小 Q 有四种花色的牌,数量分别为a,b,c,d?(别问为什么初始不是?4,当然是开挂了)。
小 Q 想知道,在手牌用完,并且正好打出了?kk?张牌的情况下,能够有多少种出牌方式。
Input
第一行输入四个整数a,b,c,d (0≤a,b,c,d≤200,a+b+c+d>0)?,代表初始手牌中每种花色的数量。
第二行输入一个整数?T(1≤T≤200)?,代表询问次数。
接下来?T?行,每行输入一个整数?k(0≤k≤1000)?,代表要手牌用完后要打出的牌数。
Output
如果能够在手牌用完的情况下,正好打出?k?张牌,输出 YES,并在下一行输出能够打出?k?张牌的方案数(相同花色的牌视为完全相同,花色排序不同即为不同),答案对 998244353?取模。
否则,输出 NO
Samples
Sample #1
Input?
0 0 2 2 3 3 4 5
Output? NO YES 2 YES 4
Sample #2
Input?
1 2 3 4 3 10 20 100
Output YES 1074 YES 3225222 YES 336967520
Hint
第一个样例中,设四种花色分别为A,B,C,D,那么初始牌数就有两张花色?CC?和两张花色?DD
当 k=3?时,是 NO 。
当 k=4?时,有 CDCD,DCDC 两种出牌方式。
当k=5?时,有 CCDCD,DDCDC,CDDCD,DCCDC 四种出牌方式。
分析:
代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Y = 998244353;
#define endl '\n'
const int N = 1010;
ll C[N][N];
ll fac[N];
ll dp[N][N];
void init() {
C[0][0] = 1;
for (int i = 1; i <= 1000; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = C[i - 1][j] + C[i - 1][j - 1], C[i][j] %= Y;
}
fac[0] = 1;
for (int i = 1; i <= 1000; i++) fac[i] = fac[i - 1] * i % Y;
}
vector<int> arr;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
init();
int ans = 0;
int mx = 0;
int zt, n;
ll res;
for (int i = 0; i < 4; i++) {
cin >> zt;
ans += zt;
mx = max(mx, zt);
if (zt) {
arr.push_back(zt);
}
}
n = arr.size();
dp[0][arr[0] - 1] = 1;
ll sum = arr[0];
// 每有一个位置(这个位置是指位置之间的缝隙)相邻位置(真位置)都有同色视为该位置有冲突
for (int i = 1; i < n; sum += arr[i++]) // 每增加一个花色
for (int j = 0; j < sum; j++) // 枚举所有冲突的数目
if (dp[i - 1][j]) // 如果前i-1个花色有j个冲突
for (int k = 1; k <= arr[i]; k++) // 枚举这个花色的组数,分成k组,每一组有多少个不重要,重要的是知道多少组就知道了多少位置(缝隙)会产生冲突了
for (int u = 0; u <= min(k, j); u++) //(选择消除u个冲突(将一部分组插入到j个冲突中))
{
ll tmp = dp [i - 1][j]; // C[j][u]选择u个冲突消除,C[arr[i]-1][k-1]是用隔板法,将arr[i]个分成k份,每一份至少为1,C[sum+1-j][k-u]是没有没有冲突的其余的插入k-u个
tmp = ((tmp * C[j][u]) % Y * (C[arr[i] - 1][k - 1] * C[sum + 1 - j][k - u] % Y)) % Y;
dp[i][j - u + arr[i] - 1 - (k - 1)] += tmp;
dp[i][j - u + arr[i] - 1 - (k - 1)] %= Y;
}
res = dp[n - 1][0];
int q;
cin >> q;
while (q--) {
int x;
cin >> x;
if (x < ans) {
cout << "NO" << endl;
continue;
}
if (mx > (ans + 1 - mx)) {
cout << "NO" << endl;
} else {
cout << "YES" << endl;
int ccc = 0;
ccc = res * C[x - n - 1][ans - n - 1] % Y;
cout << ccc << endl;
}
}
return 0;
}
新人博主多多关注点赞,以后会更新跟多文章的。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!