F - Reordering
链接: link.
题意:
给定长度为 N N N的只包含小写字母的字符串 S S S,现在问有多少个字符串是由 S S S的子序列内的字符串排列形成的
思路:
d
p
(
i
,
j
)
dp(i,j)
dp(i,j)定义为用了前
i
i
i个字母(字母表中的字母,最多为
26
26
26个),组成长度为
j
j
j的字符串的数量
d
p
(
i
,
j
)
=
∑
k
=
0
m
i
n
(
j
,
c
n
t
[
i
]
)
d
p
(
i
−
1
,
j
−
k
)
×
C
j
k
dp(i,j)=\sum_{k=0}^{min(j,cnt[i])} dp(i-1,j-k)×C_{j}^{k}\qquad
dp(i,j)=∑k=0min(j,cnt[i])dp(i−1,j−k)×Cjk
现在要往前
i
−
1
i-1
i−1个字母长度为
j
−
k
j-k
j−k的字符串中插入
k
k
k个第
i
i
i个字母了,由于之前的序列顺序通过排列组合已经有顺序了,所以只需要在长度
j
j
j的情况下,确定
k
k
k个插入的字母的相对位置,所以就是
C
j
k
C_{j}^{k}
Cjk
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
#define int long long
const int mod = 998244353;
int dp[30][N];
string s;
int cnt[30];
int fact[N], infact[N];
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
void init() {
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i++) {
fact[i] = fact[i - 1] * i % mod;
infact[i] = infact[i - 1] * qmi(i, mod - 2) % mod;
}
}
int C(int n, int m) {
if (m > n) return 0;
return fact[n] * infact[m] % mod * infact[n - m] % mod;
}
signed main() {
init();
cin >> s;
int len = s.length();
for (int i = 0; i < len; i++) {
cnt[s[i] - 'a' + 1]++;
}
dp[0][0] = 1;
for (int i = 1; i <= 26; i++) {
for (int j = 0; j <= len; j++) {
for (int k = 0; k <= min(j, cnt[i]); k++) {
dp[i][j] += dp[i - 1][j - k] * C(j, k) % mod;
dp[i][j] %= mod;
}
}
}
int res = 0;
for (int i = 1; i <= len; i++) {
res += dp[26][i];
res %= mod;
}
cout << res << endl;
}
F - Variety of Digits
链接: link.
题意:
给定数字
M
M
M个数字
现在问
1
−
N
1-N
1−N中所有包含这
M
M
M个数字且没有前导
0
0
0的数字的和
思路:
定义
d
p
(
0
,
0
/
1
,
s
t
a
t
e
)
dp(0,0/1,state)
dp(0,0/1,state)为选择的数字在二进制下位
s
t
a
t
e
state
state情况下,现在选出来的数字是从高位到枚举的位置,是否小于对应位置的数字
N
N
N的个数
定义
d
p
(
1
,
0
/
1
,
s
t
a
t
e
)
dp(1,0/1,state)
dp(1,0/1,state)为选择的数字在二进制下位
s
t
a
t
e
state
state情况下,现在选出来的数字是从高位到枚举的位置,是否小于对应位置的数字
N
N
N的和
x
x
x为枚举的数字,循环是从高位往低位进行
n
d
p
ndp
ndp数组起到滚动数组的作用
这里选的数,是指
0
−
9
0-9
0−9这些数
那么此时
n
d
p
(
0
,
f
l
a
g
∣
∣
x
<
a
[
i
]
,
s
t
a
t
e
∣
1
<
<
x
)
+
=
d
p
(
0
,
f
l
a
g
,
s
t
a
t
e
)
ndp(0,flag||x<a[i],state|1<<x)+=dp(0,flag,state)
ndp(0,flag∣∣x<a[i],state∣1<<x)+=dp(0,flag,state)
n
d
p
(
1
,
f
l
a
g
∣
∣
x
<
a
[
i
]
,
s
t
a
t
e
∣
1
<
<
x
)
+
=
d
p
(
1
,
f
l
a
g
,
s
t
a
t
e
)
∗
10
+
d
p
(
0
,
f
l
a
g
,
s
t
a
t
e
)
∗
x
ndp(1,flag||x<a[i],state|1<<x)+=dp(1,flag,state)*10+dp(0,flag,state)*x
ndp(1,flag∣∣x<a[i],state∣1<<x)+=dp(1,flag,state)∗10+dp(0,flag,state)∗x
如果是枚举的最高位置,枚举时,跳过
0
0
0即可
由于是滚动数组,所以更新完后,再把
n
d
p
ndp
ndp赋值给
d
p
dp
dp即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
string s;
int m;
int dp[2][2][1 << 10];
signed main() {
cin >> s;
int n = s.size();
vector<int> a(n);
for (int i = 0; i < n; i++) {
a[i] = s[n - i - 1] - '0';
}
cin >> m;
int mask = 0;
for (int i = 0; i < m; i++) {
int x;
cin >> x;
mask |= 1 << x;
}
for (int i = n - 1; i >= 0; i--) {
int ndp[2][2][1 << 10];
memset(ndp, 0, sizeof(ndp));
for (int x = 1; x <= (i == n - 1 ? a[i] : 9); x++) {
(ndp[0][i < n - 1 || x < a[i]][1 << x] += 1ll) %= mod;
(ndp[1][i < n - 1 || x < a[i]][1 << x] += x) %= mod;
}
for (int l = 0; l < 2; l++) {
for (int s = 0; s < (1 << 10); s++) {
for (int x = 0; x <= (l ? 9 : a[i]); x++) {
(ndp[0][l || x < a[i]][s | 1 << x] += dp[0][l][s]) %= mod;
(ndp[1][l || x < a[i]][s | 1 << x] += dp[1][l][s] * 10 + dp[0][l][s] * x) %= mod;
}
}
}
memcpy(dp, ndp, sizeof(dp));
}
int res = 0;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < (1 << 10); j++) {
if ((j & mask) == mask) {
res += dp[1][i][j];
res %= mod;
}
}
}
cout << res << endl;
}
数位
d
p
dp
dp+记忆化的思路,基本差不多
d
f
s
(
p
o
s
,
s
t
a
t
e
,
l
e
a
d
,
l
i
m
i
t
)
dfs(pos,state,lead,limit)
dfs(pos,state,lead,limit)代表枚举到了
p
o
s
pos
pos位置,选择的数状态为
s
t
a
t
e
state
state,是否有前导
0
0
0,选出来的数直到枚举位置是否小于
N
N
N
这里选出的那些数,相当于选的是那几个数是题目给的那几个,而不是全部的
0
−
9
0-9
0−9
并用
p
a
i
r
pair
pair来分别存符合的个数和求出来的和
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4 + 10;
const int mod = 998244353;
const int M = 12;
typedef pair<int, int> PII;
char s[N];
int po10[N];
int dex[M];
int m, len, n;
int all;
int dpS[N][1111], dpC[N][1111];
void init() {
po10[0] = 1;
for (int i = 1; i < N; i++) {
po10[i] = po10[i - 1] * 10ll % mod;
}
memset(dex, -1, sizeof(dex));
memset(dpS, -1, sizeof(dpS));
}
PII dfs(int pos, int state, int lead, int limit) {
if (pos > len) {
if (lead == 0 && state == all) {
return (PII){0, 1};
} else {
return (PII){0, 0};
}
}
if (!lead && !limit && dpS[pos][state] != -1) {
return {dpS[pos][state], dpC[pos][state]};
}
int res1 = 0, res2 = 0;
int up = limit ? s[pos] - '0' : 9;
for (int i = 0; i <= up; i++) {
int Newstate = state;
if (dex[i] != -1 && (i || !lead)) {
Newstate |= (1 << dex[i]);
}
PII ans = dfs(pos + 1, Newstate, lead && (i == 0), limit && (i == up));
(res2 += ans.second) %= mod;
(res1 += ans.first % mod + ans.second * i % mod * po10[len - pos]) %= mod;
}
if (!lead && !limit) {
dpS[pos][state] = res1;
dpC[pos][state] = res2;
}
return (PII){res1, res2};
}
signed main() {
init();
scanf("%s", s + 1);
scanf("%lld", &n);
len = strlen(s + 1);
all = (1 << n) - 1;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
dex[x] = i - 1;
}
cout << dfs(1, 0, 1, 1).first << endl;
}
链接: link.
E. Masha-forgetful
题意:
给定一个由长度为 N N N数字组成的字符串,现在又给 M M M个别的字符串,现在问能否用其他字符串的长度至少 2 2 2的子段来组成这个长度为 N N N的字符串,能得话,输出挑选的字段
思路:
首先任何一个大于
2
2
2的数字,都可以被表示成若干个
2
2
2和
3
3
3的和
那么你就把输入的字符串拆成长度为
2
2
2和
3
3
3的字段
那么就可以定义
d
p
(
i
)
dp(i)
dp(i)为到
i
i
i为止,字符串是否匹配
那么只要
d
p
(
i
−
2
)
=
1
dp(i-2)=1
dp(i−2)=1且
s
[
i
−
1
]
s
[
i
]
s[i-1]s[i]
s[i−1]s[i]这个字段存在,那么
d
p
(
i
)
=
1
dp(i)=1
dp(i)=1
那么只要
d
p
(
i
−
3
)
=
1
dp(i-3)=1
dp(i−3)=1且
s
[
i
−
2
]
s
[
i
−
1
]
s
[
i
]
s[i-2]s[i-1]s[i]
s[i−2]s[i−1]s[i]这个字段存在,那么
d
p
(
i
)
=
1
dp(i)=1
dp(i)=1
最后只要
d
p
(
N
)
=
1
dp(N)=1
dp(N)=1,那就从后往前还原路径就可以,提前预处理好每个子段所对应的为止就行
存位置时,可以用
t
u
p
l
e
tuple
tuple三元组来存,代码量会减少一些
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
struct node {
int x, y, z;
};
int n, m;
bool f[N];
char s[N];
int main() {
cout.tie(0)->sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
node a[10][10] = {0, 0, 0};
node b[10][10][10] = {0, 0, 0};
for (int i = 0; i < m + 2; i++) {
f[i] = 0;
}
for (int i = 1; i <= n; i++) {
cin >> s + 1;
for (int j = 1; j <= m; j++) {
if (j + 1 <= m) {
a[s[j] - '0'][s[j + 1] - '0'] = {j, j + 1, i};
}
if (j + 2 <= m) {
b[s[j] - '0'][s[j + 1] - '0'][s[j + 2] - '0'] = {j, j + 2, i};
}
}
}
cin >> s + 1;
f[0] = 1;
int x, y, z;
for (int i = 1; i <= m; i++) {
if (i >= 2) {
x = a[s[i - 1] - '0'][s[i] - '0'].x;
y = a[s[i - 1] - '0'][s[i] - '0'].y;
z = a[s[i - 1] - '0'][s[i] - '0'].z;
if (f[i - 2] == 1 && x != 0 && y != 0 && z != 0) {
f[i] = 1;
}
}
if (i >= 3) {
x = b[s[i - 2] - '0'][s[i - 1] - '0'][s[i] - '0'].x;
y = b[s[i - 2] - '0'][s[i - 1] - '0'][s[i] - '0'].y;
z = b[s[i - 2] - '0'][s[i - 1] - '0'][s[i] - '0'].z;
if (f[i - 3] == 1 && x != 0 && y != 0 && z != 0) {
f[i] = 1;
}
}
}
if (f[m] == 0) {
cout << "-1" << endl;
} else {
vector<node> ans;
int i = m;
int x, y, z;
while (i) {
x = a[s[i - 1] - '0'][s[i] - '0'].x;
y = a[s[i - 1] - '0'][s[i] - '0'].y;
z = a[s[i - 1] - '0'][s[i] - '0'].z;
if (i >= 2 && f[i - 2] && x != 0 && y != 0 && z != 0) {
ans.push_back({x, y, z});
i -= 2;
} else {
x = b[s[i - 2] - '0'][s[i - 1] - '0'][s[i] - '0'].x;
y = b[s[i - 2] - '0'][s[i - 1] - '0'][s[i] - '0'].y;
z = b[s[i - 2] - '0'][s[i - 1] - '0'][s[i] - '0'].z;
ans.push_back({x, y, z});
i -= 3;
}
}
reverse(ans.begin(), ans.end());
cout << ans.size() << endl;
for (int i = 0; i < ans.size(); i++) {
cout << ans[i].x << " " << ans[i].y << " " << ans[i].z << endl;
}
}
}
}
To be continued
如果你有任何建议或者批评和补充,请留言指出,不胜感激