hncpc2021(A~G、J)

题目连接


A、十二强赛(模拟)

在这里插入图片描述

题意:

​ 略。

分析:

​ 模拟即可,不多说。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 1e9 + 7;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//
void slove() {
    vector<int> sum(6), s1(6), s2(6);
    for(int i=0;i<15;i++) {
        char a, b;
        int w1, w2, w3, w4;
        cin >> a >> b >> w1 >> w2 >> w3 >> w4;
        s1[a - 'A'] += w1 + w3;
        s2[a - 'A'] += w2 + w4;
        s1[b - 'A'] += w2 + w4;
        s2[b - 'A'] += w1 + w3;
        if(w1 > w2) {
            sum[a - 'A'] += 3;
        } else if (w1 == w2) {
            sum[a - 'A'] ++;
            sum[b - 'A'] ++;
        } else {
            sum[b - 'A'] += 3;
        }
        if(w3 > w4) {
            sum[a - 'A'] += 3;
        } else if (w3 == w4) {
            sum[a - 'A'] ++;
            sum[b - 'A'] ++;
        } else {
            sum[b - 'A'] += 3;
        }
    }

    for(int i=0;i<6;i++) {
        cout << sum[i] << " " << s1[i] - s2[i] << " " << s1[i] << '\n';
    }
}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int t;
    cin >> t;
    while(t --) {
        slove();
    }


    return 0;
}


B、方格填数(思维)

在这里插入图片描述

题意:

​ 略 (中文不用解释吧)。

分析:

​ 很显然,填入一个数后,左上的数必须小于这个数,右下的数必须大于这个数,直接判断是否合法就行了。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 1e9 + 7;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//

int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int n, m, r, c, x;
    while(cin >> n >> m >> r >> c >> x) {
        int p1 = r * c - 1;
        int p2 = (n - r + 1) * (m - c + 1) - 1;
        if(p1 > x - 1 || p2 > (n  * m - x)) {
            cout << "No\n";
        } else {
            cout << "Yes\n";
        }
    }


    return 0;
}


C、Average of Two Numbers(STL)

在这里插入图片描述

题意:

​ 有 n 个不同的数,找出有多少个数可以作为这些数中任意两个其他数字的平均值。

分析:

​ 直接用set即可,具体看代码。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 1e9 + 7;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//

void slove(int n) {
    vector<int> a(n);
    set<int> s;
    for(int i=0;i<n;i++) {
        cin >> a[i];
        s.insert(a[i]);
    }

    for(int i=0;i<n;i++) {
        for(int j=i+1;j<n;j++) {
            if((a[i] + a[j]) % 2 == 0 ) {
                int x = (a[i] + a[j]) / 2;
                if(s.count(x)) {
                    s.erase(s.find(x));
                }
            }
        }
    }
    cout << n - s.size() << '\n';
}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int n;
    while(cin >> n) {
        slove(n);
    }

    return 0;
}

D、Sum Them(逆元)

在这里插入图片描述

题意:

​ 给你 n、m,要你求这个式子的值。

分析:

​ 发现在 i 增加时,后面的式子只改变了一个值,我们只需在增加时除以最左边的,然后乘以新的右边的,相加即可。

​ **注意:**这里是取模下的乘法,要用乘法逆元,直接用费马小定理会超时,线性预处理即可。

乘法逆元(不会点这里)

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 1e9 + 7;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//

void slove(int n, int m) {

    vector<LL> inv(n + m + 1);
    inv[0] = inv[1] = 1;
    for(int i = 2; i <= n + m; i ++ ) {
        inv[i] = mod - mod / i * inv[mod % i] % mod;
    }

    LL res = 1, ans = 0;
    for(int i=1;i<=m;i++) {
        res = res * i % mod;
    }

    ans = (ans + res) % mod;
    for(int i=2;i<=n;i++) {
        res = res * inv[i - 1] % mod * (i + m - 1) % mod;
        ans = (ans + res) % mod;
    }

    cout << ans << '\n';

}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int n, m;
    while(cin >> n >> m) {
        slove(n, m);
    }
    
    return 0;
}


E、Not the Only Tree(思维)

在这里插入图片描述

题意:

​ 给出一个初始的二叉搜索树输入序列,问你有多少种不同的输入序列,构建出来的二叉树和初始的一样,保证给出序列是一个排列。

分析:

​ 经过思考后我们发现,对于某个点来说,只要左子树的相对顺序和右子树的相对顺序不变,那么构建出来的二叉搜索树是不变的。设左子树的数量为 x x x,右子树的数量为 y y y f ( x ) f(x) f(x)为点 x x x的方案数, l s ls ls 为点x左子树的根, r s rs rs为点x右子树的根,那么点 x x x的方案数为 f ( x ) = f ( l s ) ∗ f ( r s ) ∗ C x + y x f(x)=f(ls)*f(rs)*C_{x+y}^x f(x)=f(ls)f(rs)Cx+yx

​ 我们可以根据给出的序列构建出这个二叉搜索树,然后从根开始递归求解即可。具体看代码。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 1e9 + 7;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//
int norm(int x) {
    if (x < 0) {x += mod;}
    if (x >= mod) {x -= mod;}
    return x;
}
template<class T>
T ksm(T x,LL y){
    T res = 1;
    for(;y;y>>=1){
        if(y&1)
            res = res*x;
        x=x*x;
    }
    return res;
}
struct modint {
    int x;
    modint(int x = 0) : x(norm(x)) {}
    modint(LL x) : x(norm((int)(x%mod))) {}
    int val() const {return x;}
    modint operator-() const {return modint(norm(mod - x));}
    modint inv() const {assert(x != 0);return ksm(*this, mod - 2);}
    modint &operator*=(const modint &rhs) {x = LL(x) * rhs.x % mod;return *this;}
    modint &operator+=(const modint &rhs) {x = norm(x + rhs.x);return *this;}
    modint &operator-=(const modint &rhs) {x = norm(x - rhs.x);return *this;}
    modint &operator/=(const modint &rhs) {return *this *= rhs.inv();}
    friend modint operator*(const modint &lhs, const modint &rhs) {modint res = lhs;res *= rhs;return res;}
    friend modint operator+(const modint &lhs, const modint &rhs) {modint res = lhs;res += rhs;return res;}
    friend modint operator-(const modint &lhs, const modint &rhs) {modint res = lhs;res -= rhs;return res;}
    friend modint operator/(const modint &lhs, const modint &rhs) {modint res = lhs;res /= rhs;return res;}
    friend std::istream &operator>>(std::istream &is, modint &a) {LL v;is >> v;a = modint(v);return is;}
    friend std::ostream &operator<<(std::ostream &os, const modint &a) {return os << a.val();}
};
void slove(int n) {
    vector<int> a(n);

    for(int i=0;i<n;i++) {
        cin >> a[i];
    }

    int cnt = 0;
    vector<int> siz(n + 1), l(n + 1), r(n + 1), val(n + 1);

    function<int(int, int)> dfs = [&](int rt, int x) {
        if(!rt) {
            rt = ++ cnt;
            siz[rt] = 1;
            val[rt] = x;
            return rt;
        }
        siz[rt] ++;
        if(x < val[rt]) {
            l[rt] = dfs(l[rt], x);
        } else {
            r[rt] = dfs(r[rt], x);
        }
        return rt;
    };

    dfs(0, a[0]);
    for(int i=1;i<n;i++) {
        dfs(1, a[i]);
    }

    vector<modint> fac(n + 1), ifac(n + 1);
    fac[0] = 1;
    for(int i=1;i<=n;i++) {
        fac[i] = fac[i - 1] * i;
    }
    ifac[n] = fac[n].inv();
    for(int i=n-1;i>=0;i--) {
        ifac[i] = ifac[i + 1] * (i + 1);
    }

    auto C = [&] (int x, int y) -> modint{
        return fac[x] * ifac[x - y] * ifac[y];
    };

    function<modint(int)> dfs1 = [&] (int rt) -> modint {
        if(rt == 0) {
            return 1;
        }
        return dfs1(l[rt]) * dfs1(r[rt]) * C(siz[l[rt]] + siz[r[rt]], siz[l[rt]]);
    };

    cout << dfs1(1) << '\n';

}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int n;
    while(cin >> n) {
        slove(n);
    }

    return 0;
}

F、最大的数(贪心)

题意:

​ 给你一个十进制数 n n n,可以交换两个距离不超过d的数字,每个数字只能被交换一次,求最大的数。

分析:

​ 首先,我们很容易想到,越靠前的的数字,交换它范围内能够交换且还没有交换过的数字。这样想没问题,但还有一些细节问题,看这个例子:21499,1和2都能与后面两个9交换,显然最优的情况时1与最后面那个9交换,如果我们直接从前往后交换会出现问题,所以我们还需要从后往遍历一次,具体过程如下:

  1. 创建09的数字栈,并创建09的标记栈,从前往后找出每个数字在范围内能交换的最大数,并且进入标记栈。
  2. 我们已经找出了每个数字要与哪些位置交换,这时,我们创建0~9的优先队列,从后往前遍历,每次弹出最小的进行交换,此时就可以保证最优。

​ 具体细节见代码。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 998244353;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//

void slove(string s, int d) {
    string ans = s;

    vector<stack<int>> st(10); // 栈
    vector<priority_queue<pii, vector<pii>, greater<pii>>> pq(10);
    vector<vector<int>> a(10);

    d ++;
    int n = s.size();
    for(int i = 1;i <= min(d, n - 1); i ++) {
        st[s[i] - '0'].push(i);
    }

    vector<int> vis(n); // 标记是否被交换
    for(int i = 0; i < n; i ++) { // 第一遍找出每个元素的交换数字
        if(i > 0 && i + d < n) {
            st[s[i + d] - '0'].push(i + d);
        }
        if(!vis[i]) {
            for(int j = 9; j > s[i] - '0'; j --) {
                if(!st[j].empty() && st[j].top() > i) {
                    vis[st[j].top()] = 1;
                    a[j].push_back(i); // 预定
                    st[j].pop();
                    break;
                }
            }
        }
    }

    for(int i = n - 1; i >= 0; i --) { // 交换已经确定,选择更加好的交换 越后面的换越小的
        if(vis[i]) { // 这个需要交换
            int x = s[i] - '0';
            while(a[x].size() && a[x].back() + d >= i) {
                int w = a[x].back();
                pq[x].push({s[w] - '0', w});
                a[x].pop_back();
            }
            auto y = pq[x].top();
            pq[x].pop();
            swap(ans[y.se], ans[i]);
        }

    }

    cout << ans << '\n';
}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    string s;
    int d;
    while(cin >> s >> d) {
        slove(s, d);
    }

    return 0;
}

G洞穴探宝(状态压缩)

题意:

​ 给你一个地图,‘.’ 为路,'#'为墙,‘X’为陷阱,’@'为宝藏,题目保证只有一个入口,墙不通,陷阱只能通过一次,在宝藏周围也可以也可以捡到宝藏,必须从入口进入和从入口出来,问最多能拿多少宝藏。

分析:

​ 根据题目数据看出宝藏和陷阱的数量较少,可以直接建图然后状态压缩一下就行了,这题主要是代码量大,方法很多,我简单说一下我的方法:

​ 把起点、宝藏、陷阱看做点,边为直接能用’ . '走到,然后dfs搜一下就可以了。具体见代码。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 998244353;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//
void slove(int n, int m) {

    vector<vector<char>> a(n, vector<char>(m, 0));

    int sx = 0, sy = 0; // 起始点
    for(int i=0;i<n;i++) {
        for(int j=0;j<m;j++) {
            cin >> a[i][j];
            if((i == 0 || i == n - 1 || j == 0 || j == m - 1) && a[i][j] == '.') {
                sx = i, sy = j;
            }
        }
    }

    vector<vector<int>> vis(n, vector<int>(m, 0));
    auto vis1 = vis;

    int cnt = 0;
    vector<int> res(30);//是否是陷阱
    vector<vector<int>> g(30), bi(n, vector<int>(m, -1)); // 建图以及编号数组
    vector<pii> id(30);// 每个编号对应坐标
    bi[sx][sy] = 0; // 起始点看做一个点
    id[0] = {sx, sy};
    // 先标号
    for(int i=0;i<n;i++) {
        for(int j=0;j<m;j++) {
            if(a[i][j] != '.' && a[i][j] != '#') {
                bi[i][j] =  ++ cnt;
                id[cnt] = {i, j};
                res[cnt] = (a[i][j] == 'X');
            }
        }
    }

    function<void(int, int, int)> dfs1 = [&] (int id, int x, int y) {
        vis[x][y] = 1;
        for(int k = 0; k < 4; k ++) {
            int tx = x + di[k];
            int ty = y + di[k + 1];
            if(tx < 0 || tx >= n || ty < 0 || ty >= m || vis[tx][ty] || a[tx][ty] == '#') {
                continue;
            }

            if(bi[tx][ty] != -1) {
                g[id].push_back(bi[tx][ty]);
                continue;
            }
            dfs1(id, tx, ty);
        }
    };

    for(int i=0;i<n;i++) {
        for(int j=0;j<m;j++) {
            if(bi[i][j] >= 0) { // 建图
                vis = vis1;
                dfs1(bi[i][j], i, j);
            }
        }
    }

    cnt ++;

    vector<vector<int>> f(1 << cnt, vector<int>(cnt, -1)); // 遍历过的点的状态, 当前点
    vector<int> vis2(30);
    int ans = 0;

    function<void(int, int)> dfs2 = [&] (int s, int x) {
        if(f[s][x] != -1) { //这个状态遍历过
            return ;
        }
        f[s][x] = 1;
        if(x == 0) { // 走到了出口
            int res1 = 0;
            fill(all(vis2), 0);
            for(int i=1;i<cnt;i++) {
                if((s >> i) & 1) {
                    if(!res[i]) res1 ++ ;
                    auto [wx, wy] = id[i];
                    for(int k = 0; k < 4 ;k ++) { // 找每个点周围的宝藏
                        int tx = wx + di[k];
                        int ty = wy + di[k + 1];
                        if(tx < 0 || tx >=n || ty < 0 || ty >= m || a[tx][ty] != '@') {
                            continue;
                        }
                        int si = bi[tx][ty];
                        if(!((s >> si) & 1) && !vis2[si]) {
                            res1 ++;
                            vis2[si] = 1;
                        }
                    }
                }
            }
            ans = max(ans, res1);
        }

        for(auto y : g[x]) {
            if(((s >> y) & 1) && res[y]) { // 陷阱不能重复走
                continue;
            }
            dfs2(s | (1 << y), y);
        }

    };

    dfs2(1, 0);

    cout << ans << '\n';

}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int n, m;
    while( cin >> n >> m) {
        slove(n, m);
    }

    return 0;
}

H、 Circle Intersection(计算几何)

在这里插入图片描述

目前还不会。。。。

I、戴森球计划(K - D树)

在这里插入图片描述

目前不会。

J、String Set(广义SAM / AC自动机)

在这里插入图片描述

题意:

​ 有一个字符串的集合,有以下三种操作:

​ I s :在集合中插入一个字符串 s 。

​ D s: 删除在集合中字符串中子串包含 s 的。

​ Q s:查询集合中字符串包含子串 s 的数量。

思路:

​ (本题目官方解法是广义SAM,我目前还不会,也没有想到什么好的SA解法,偶然在知乎上面看到有人说可以用AC自动机离线的做法,很快写了出来)

​ 采用离线的思路,将所有询问串和删除串分别建立AC自动机,对于每一个插入串,找到它后面第一个出现的并能够将他删除的操作,然后找到这个区间内所有与这个插入串匹配的询问串,添加答案即可。

​ 有些细节,具体见代码。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 998244353;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//

int ans[maxn], mi; // 答案数组和区间标记


struct AC {
    const int N = 1e6 + 10;
    vector<vector<int>> t, num;
    vector<int> fail;
    int tot = 1;
    AC () {
        num.resize(N);
        fail.resize(N);
        t.resize(N);
        for(int i = 0; i < N; i ++) {
            t[i].resize(26);
        }
    }
    void insert(string s, int id){
        int p = 1, len = s.size();
        for(int i = 0; i < len; i ++) {
            int ch = s[i] - 'a';
            if(!t[p][ch]) {
                t[p][ch] = ++ tot;
            }
            p = t[p][ch];
        }
        num[p].push_back(id);
    }
    void getfail(){
        for(int i = 0; i < 26; i ++ ) {
            t[0][i] = 1;
        }
        queue<int> q;
        q.push(1);
        fail[1] = 0;
        while(q.size()){
            int u = q.front();
            q.pop();
            for(int i = 0; i < 26; i ++){//遍历所有儿子
                int v = t[u][i];
                int Fail = fail[u];//失配结点 tire[fail].son[i] 与i相同
                if(!v) {//没有的话 通过父亲对的失配来连接
                    t[u][i] = t[Fail][i];
                    continue;
                }
                fail[v] = t[Fail][i];//存在 直接指
                q.push(v);
            }
        }
    }
    void query(string s, int l, int r, int op){
        vector<int> ed(tot + 1); // 防止重复计算
        int p = 1, len = s.size();
        for(int i = 0; i < len; i ++){
            int v = s[i]-'a';
            int k = t[p][v];//跳fail
            while(k > 1 && !ed[k]){ // 不唯一
                for(auto x : num[k]) {
                    if(op == 0) { // 删除的串
                        if(x >= l && x <= r) { // 注意范围
                            mi = min(mi, x);
                        }
                    } else { // 询问串
                        if(x >= l && x <= r) { // 注意范围
                            ans[x] ++; // 直接添加
                        }
                    }
                }
                ed[k] = 1; // 标记
                k = fail[k];
            }
            p = t[p][v];
        }
    }
};
void slove() {
    int n;
    cin >> n;

    vector<char> op(n);
    vector<string> s(n);

    AC t1, t2;

    for(int i=0;i<n;i++) {
        cin >> op[i] >> s[i];
        if(op[i] == 'Q') t1.insert(s[i], i);
        if(op[i] == 'D') t2.insert(s[i], i);
    }

    t1.getfail();
    t2.getfail();

    for(int i=0;i<n;i++) {
        if(op[i] == 'I') {
            mi = 1e9;
            t2.query(s[i], i, n, 0); // 找范围
            t1.query(s[i], i, mi - 1, 1); // 直接统计
        }
    }

    for(int i=0;i<n;i++) {
        if(op[i] == 'Q') {
            cout << ans[i] << '\n';
        }
    }
}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int t = 1;
   // cin >> t;
    while(t -- ) {
        slove();
    }

    return 0;
}

K双排积木(插头DP)

目前不会。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值