题目连接
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交换,如果我们直接从前往后交换会出现问题,所以我们还需要从后往遍历一次,具体过程如下:
- 创建09的数字栈,并创建09的标记栈,从前往后找出每个数字在范围内能交换的最大数,并且进入标记栈。
- 我们已经找出了每个数字要与哪些位置交换,这时,我们创建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)
目前不会。