2021杭电多校赛第三场
2021“MINIEYE杯”中国大学生算法设计超级联赛(3)
Forgiving Matching
由于要求的是可以有\(k\)个不同字符的匹配,我们可以反向思考,先计算当前匹配串\(s\)的所有子串\(s_i\)和模式串\(t\)的最多相同个数\(f_i\),即相同位置相同字符或者有一个是通配符\(*\)的个数\((f(abc,acc)=2\),\(f(a*b,ab*)=3)\),例如样例所求出来的\(f\)就是\([1,3,1]\),然后用模式串\(t\)的长度\(m\)减去\(t\)的每个子串的最大匹配个数就是不同字符个数,然后我们用一个数组\(ans\)存储这些答案,\(ans[i]\)表示\(i\)个字符不同的\(match\)个数,因为题目要输出的是\(\leq k\)的个数,所以我们只需要对\(ans\)再求个前缀和就好了。
具体地,我们可以用以下地方法求出\(f\):
翻转\(s\)或者\(t\)让接下来构造的数组满足卷积的形式,一般翻转模式串\(t\),对于为什么要翻转不懂的话可以先写下洛谷的残缺的字符串。
考虑单独计算每个字符的贡献,其中有两种情况:
\(\bullet\) \(0\cdots9\):
假如当前计算的数字为\(5\),那么用于卷积的两个数组\(a_i\)和\(b_i\)可以由对应的\(s_i\)和\(t_i\)得到,如果\(s_i\)不是\(5\),那么\(a_i\)为0,否则为\(1\),\(b_i\)同理,这样可以保证我们卷积后的多项式系数就代表这个子串有多少个\(5\)是在相同位置上的,其他数字也用同样的方法计算得到,然后将它们的贡献累加保存在\(f\)中。其中对于下标有一些细节。
\(\bullet\) \(*\):
也是和上面一个情况的同样计算方法,但是计算贡献有所不同,因为算出来的是相同位置相同字符的个数,而\(*\)可以匹配任意一个字符,如果是\(*a\),\(b*\),算出来结果会是\(0\),而我们期望的结果是\(2\),所以我们可以采用容斥的思想,贡献就是卷积后两个子串\(*\)的个数减去当前子串的多项式的系数。其中,计算匹配串\(s\)的子串通配符\(*\)可以利用前缀和快速计算,而模板串\(t\)的通配符\(*\)个数是定量,直接用一个变量\(cnt\)存储即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
namespace NTT {
const int MAXN = 1e6 + 5;
const int MOD = 998244353;
const int G = 3;
template<typename T>
T fpow(T a, int n) {
T res = 1;
while (n) {
if (n & 1) {
res = (res * a) % MOD;
}
a = (a * a) % MOD;
n >>= 1;
}
return res;
}
int inv(int a) {
return fpow(a, MOD - 2);
}
struct Complex {
double x, y;
Complex(double _x, double _y): x(_x), y(_y) {}
Complex operator +(Complex oth) {
return Complex(x + oth.x, y + oth.y);
}
Complex operator -(Complex oth) {
return Complex(x - oth.x, y - oth.y);
}
Complex operator *(Complex oth) {
return Complex(x * oth.x - y * oth.y, x * oth.y + y * oth.x);
}
};
int R[MAXN], L, limit = 1;
void NTT(int a[], int opt) {
for (int i = 0; i < limit; ++i) {
if (i < R[i]) {
swap(a[i], a[R[i]]);
}
}
for (int mid = 1; mid < limit; mid <<= 1) {
int val = fpow(G, (MOD - 1) / (mid * 2));
if (opt == -1) val = inv(val);
for (int len = mid << 1, pos = 0; pos < limit; pos += len) {
for (int k = 0, w = 1; k < mid; ++k, w = w * val % MOD) {
int x = a[pos + k], y = w * a[pos + mid + k] % MOD;
a[pos + k] = (x + y) % MOD;
a[pos + k + mid] = (x - y + MOD) % MOD;
}
}
}
if (opt == 1) return;
int t = inv(limit);
for (int i = 0; i < limit; ++i) {
a[i] = a[i] * t % MOD;
}
}
void poly(int a[], int b[], int c[], int deg) {
while (limit <= deg) {
limit <<= 1, ++L;
}
for (int i = 0; i < limit; ++i) {
R[i] = (R[i >> 1] >> 1) | ((i & 1) << (L - 1));
}
NTT(a, 1);
NTT(b, 1);
for (int i = 0; i < limit; ++i) {
c[i] = a[i] * b[i] % MOD;
}
NTT(c, -1);
// NTT的两个数组会重复使用所以要初始化
for (int i = 0; i <= limit; ++i) {
a[i] = b[i] = 0;
}
}
} // namespace NTT
using NTT::poly;
const int MAXN = 1e6 + 5;
char s[MAXN], t[MAXN];
int a[MAXN], b[MAXN], c[MAXN];
int ans[MAXN], sum[MAXN], f[MAXN];
int32_t main(int argc, char *argv[]) {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m >> s >> t;
// 多组数据初始化
for (int i = 0; i <= n; ++i) {
f[i] = sum[i] = ans[i] = 0;
}
// 翻转模式串
reverse(t, t + m);
int cnt = 0; // 记录模板串*个数
for (int i = 0; i < m; ++i) {
if (t[i] == '*') {
++cnt;
}
}
for (int i = 0; i < n; ++i) {
sum[i + 1] = sum[i] + (s[i] == '*');
}
for (auto ch: "0123456789*") {
for (int i = 0; i < n; ++i) {
a[i] = (s[i] == ch ? 1 : 0);
}
for (int i = 0; i < m; ++i) {
b[i] = (t[i] == ch ? 1 : 0);
}
poly(a, b, c, n + m);
for (int i = m - 1; i <= n - 1; ++i) {
if (ch == '*') {
int j = i - m + 2; // 匹配串开始的下标
// 匹配串*个数 + 模式串*个数 - 相同位置*个数
f[i] += (sum[j + m - 1] - sum[j - 1]) + cnt - c[i];
}
else {
f[i] += c[i];
}
}
}
for (int i = m - 1; i <= n - 1; ++i) {
// 计算不匹配字符个数
++ans[m - f[i]];
}
for (int i = 0; i <= m; ++i) {
// 先累加上再计算
if (i) ans[i] += ans[i - 1];
cout << ans[i] << '\n';
}
}
system("pause");
return 0;
}
Rise in Price
因为题目保证数据随机,根据官方题解所说,我们只需要维护当前状态最大的前几个状态,最终答案大概率就在这些状态中,我们每次保存从上次状态转移来的前\(100\)个最大状态,不断进行维护即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e2 + 5;
typedef pair<int, int> PII;
vector<PII> dp[MAXN][MAXN]; // pair存储每个状态的钻石个数和单价
int a[MAXN][MAXN], b[MAXN][MAXN];
int calc(PII p) {
return p.first * p.second;
}
void solve(int x, int y, vector<PII> &X, vector<PII> &Y, vector<PII> &Z) {
int i = 0, j = 0;
while ((i < X.size() || j < Y.size()) && Z.size() < 100) {
if (i < X.size() && j < Y.size()) {
Z.push_back(calc(X[i]) > calc(Y[j]) ? X[i++] : Y[j++]);
}
else if (i < X.size()) {
Z.push_back(X[i++]);
}
else if (j < Y.size()) {
Z.push_back(Y[j++]);
}
}
for (auto &it: Z) {
it.first += a[x][y];
it.second += b[x][y];
}
}
bool cmp(PII lhs, PII rhs) {
return lhs.first * lhs.second > rhs.first * rhs.second;
}
int32_t main(int argc, char *argv[]) {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> a[i][j];
dp[i][j].clear(); // 多组数据记得初始化
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> b[i][j];
}
}
dp[1][1].push_back({a[1][1], b[1][1]});
for (int i = 2; i <= n; ++i) { // 预处理第一行
auto it = dp[1][i - 1][0];
dp[1][i].push_back({it.first + a[1][i], it.second + b[1][i]});
}
for (int i = 2; i <= n; ++i) { // 预处理第一列
auto it = dp[i - 1][1][0];
dp[i][1].push_back({it.first + a[i][1], it.second + b[i][1]});
}
for (int i = 2; i <= n; ++i) {
for (int j = 2; j <= n; ++j) {
solve(i, j, dp[i - 1][j], dp[i][j - 1], dp[i][j]);
sort(dp[i][j].begin(), dp[i][j].end(), cmp);
}
}
int res = 0;
for (auto it: dp[n][n]) {
res = max(res, it.first * it.second);
}
cout << res << '\n';
}
system("pause");
return 0;
}

浙公网安备 33010602011771号