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;
}
posted @ 2021-08-05 19:22  stler  阅读(90)  评论(0)    收藏  举报