1 CF204E
求n个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少k个字符串的子串。
我们进行线段树合并出每个 状态所包含的是哪个串 统计出是否大于k
如果 大于k我们直接 加上len的贡献 如果没有标记的话就是 最近的有标记的父亲
2 CF666E
给出一个串s
和几个串ti
求出串s [l,r] 在串 ti出现次数最大
相同输出最左边的
线段树合并 每个节点代表的是 该点 在哪个字符串中出现(出现的位置) 并且维护出出现的次数 那么 我们进行合并的时候考虑二维就可以了
我们可以倍增锁定位置
3 P3975 [TJOI2015]弦论
我们分情况讨论
如果t=0
那么就是本质不同的串 我们每个点的点权为 1
如果t=1
包含位置不同的串 我们每个点的点权可以dfs 或者基数排序求
我们dfs(也可以dag上dp) 一遍 求出 每个状态和 表示 点u能到达的点的数量
那么我们最后递归处理的时候 如果k<=siz[1] 直接返回-1
否则我们贪心的递归输出
4 P4070 [SDOI2016]生成魔咒
每增加一个串 就会增加 len[np]-len[tr[np].fa] 个字符串
5CF235C Cyclical Quest
给定一个主串S和n个询问串,求每个询问串的所有循环同构在主串中出现的次数总和。
很套路的东西 我们可以直接在t串后面接一个串 然后我们跑t串 每次倍增
找到 加入 t[i] 后 长度为l的状态 直接给该状态 打上标记 加上该状态贡献
注意如果 没有找到 下一个状态记得将 该指针置为1
6P4094 [HEOI2016/TJOI2016]字符串
给定串s 和q 个询问
每次问s[a…b]的所有子串和s[c…d]的最长公共前缀最大值
将串反转处理 那么我们每次可以二分出一个mid 倍增可以找到s[i-mid+1,i]
预处理的话线段树合并求出每个点的endpos集合
那么我们只需要判断一下endpos集合里包含 [a+mid-1,b]不是[a,b]
如果小于a+mid-1 那么这个字符串的左端有可能小于a 就错了
还有线段树合并的时候如果 不建立新节点的写法可能会tle
7葫芦的考验之定位子串2.0
求子串 s[l ,r] 在 s[x,y]的出现次数
上个题的降维版本
线段树合并预处理endpos
我们倍增可以锁定 s[l,r]的状态
直接查询 [x+mid-1,y]
常用的 线段树合并
struct seg {
void pushup(int x) {
sum[x] = sum[ls[x]] + sum[rs[x]];
}
void change(int i, int l, int r, int x) {
if(!i) i = ++idx;
if(l == r) {
sum[x] = 1;
return ;
}
int mid = l + r >> 1;
if(x <= mid) change(ls[i], l, mid, x);
else change(rs[i], mid + 1, r, x);
pushup(i);
}
int query(int i, int l, int r, int ql, int qr) {
if(l <= ql && qr <= r) {
return sum[x];
}
int mid = l + r >> 1;
int ans = 0;
if(l <= mid) ans += query(ls[i], l, mid, ql, qr);
if(r > mid) ans += query(rs[i], mid + 1, r, ql, qr);
return ans;
}
int merge(int x, int y, int l, int r) {
if(!x || !y) return x + y;
int p = ++idx;
int mid = l + r >> 1;
if(l == r) {
sum[p] = sum[x] | sum[y];
return p;
}
ls[p] = merge(ls[x], ls[y], l, mid);
rs[p] = merge(rs[x], rs[y], mid + 1, r);
pushup(p);
return p;
}
};
8"蔚来杯"2022牛客暑期多校训练营3H
板子题
贴代码了
t串在s串上跑
直接st查询最小值 即可
#include
using namespace std;
//#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
#define fi first
#define se second
#define pb push_back
#define inf 1ll<<62
#define endl "\n"
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define de_bug(x) cerr << #x << "=" << x << endl
#define all(a) a.begin(),a.end()
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fer(i,a,b) for(int i=a;i<=b;i++)
#define der(i,a,b) for(int i=a;i>=b;i--)
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
int n, m , k;
string s;
int cnt = 1;
int last = 1;
struct node {
int fa, len;
int son[26] = {};
} tr[N];
map<int, int>mp;
int idx;
int siz[N];
void extend (int c) {
int p = last, np = last = ++cnt;
tr[np].len = tr[p].len + 1;
mp[++idx] = np;
siz[np] = 1;
for(; p && !tr[p].son[c]; p = tr[p].fa) tr[p].son[c] = np;
if(!p) tr[np].fa = 1;
else {
int q = tr[p].son[c];
if(tr[q].len == tr[p].len + 1)tr[np].fa = q;
else {
int nq = ++cnt;
tr[nq] = tr[q];
tr[nq].len = tr[p].len + 1;
tr[q].fa = tr[np].fa = nq;
for(; p && tr[p].son[c] == q; p = tr[p].fa) tr[p].son[c] = nq;
}
}
}
ll mi[N][20];
ll sum[N];
int lg[N];
ll query(int l, int r) {
int t = lg[r - l + 1];
return min(mi[l][t], mi[r - (1 << t) + 1][t]);
}
void solve() {
for(int i = 2; i < N; i++) {
lg[i] = lg[i >> 1] + 1;
}
cin >> n >> m >> k;
string s;
cin >> s;
for(auto c : s) {
extend(c - 'a');
}
for(int i = 1; i <= m; i++) {
cin >> sum[i];
sum[i] += sum[i - 1];
mi[i][0] = sum[i - 1];
}
for(int i = 1; i <= 20; i++)
for(int j = 1; j + (1 << i) - 1 <= m; j++) {
mi[j][i] = min(mi[j][i - 1], mi[j + (1 << (i - 1))][i - 1]);
}
while(k--) {
string t;
cin >> t;
ll ans = 0;
int p = 1;
int l = 0;
for(int i = 0; i < m; i++) {
while(p && !tr[p].son[t[i] - 'a']) {
p = tr[p].fa;
l = tr[p].len;
}
if(tr[p].son[t[i] - 'a']) {
l++;
p = tr[p].son[t[i]-'a'];
} else l = 0, p = 1;
if(l)
ans = max(ans, sum[i + 1] - query(i - l + 2, i + 1));
}
cout << ans << endl;
}
}
int main() {
IOS;
int _ = 1;
//cin>>_;
while( _-- )
solve();
}
9CF802I Fake News (hard)
sam板子 直接求出每个状态的大小那么 这个状态的贡献就是 siz[u]*siz[u]*len[u]-len[tr[u].fa]
10CF1037H Security
线段树合并 求个每个状态的endpos集合
因为字典序要严格大于t串
匹配t串 求出在S[L,R]出现过的最长串的状态
然后我们贪心的考虑 如果刚好匹配成功 那么 我们只需下一位从 0开始就行
否则我们用一个栈去维护 从后往前 依次 去枚举 比当前位大并且 出现在s[l,r]
的串
21 沈阳String Problem
贪心走 字典序大的 第一走到的肯定是最大后缀
#include
using namespace std;
//#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
#define fi first
#define se second
#define pb push_back
#define inf 1ll<<62
#define endl "\n"
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define de_bug(x) cerr << #x << "=" << x << endl
#define all(a) a.begin(),a.end()
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fer(i,a,b) for(int i=a;i<=b;i++)
#define der(i,a,b) for(int i=a;i>=b;i--)
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int n, m , k;
string s;
string t;
struct node {
int len, fa;
int son[26];
int pos;
} tr[N * 2];
int cnt = 1;
int last = 1;
int ans[N];
int pos[N];
void extend (int c) {
int p = last, np = last = ++cnt;
tr[np].pos = tr[np].len = tr[p].len + 1;
for(; p && !tr[p].son[c]; p = tr[p].fa) tr[p].son[c] = np;
if(!p) tr[np].fa = 1;
else {
int q = tr[p].son[c];
if(tr[q].len == tr[p].len + 1)tr[np].fa = q;
else {
int nq = ++cnt;
tr[nq] = tr[q];
tr[nq].len = tr[p].len + 1;
tr[q].fa = tr[np].fa = nq;
for(; p && tr[p].son[c] == q; p = tr[p].fa) tr[p].son[c] = nq;
}
}
}
int vis[N * 2];
void sol(int x, int l) {
vis[x] = 1;
for(int i = 25; i >= 0; i--) {
if(!tr[x].son[i] || vis[tr[x].son[i]] )continue;
sol(tr[x].son[i], l + 1);
}
if(ans[tr[x].pos ] == 0) {
ans[tr[x].pos ] = tr[x].pos - l + 1 ;
}
}
void solve() {
cin >> s;
for(auto c : s) {
extend(c - 'a');
}
sol(1, 0);
for(int i = 1; i <= (int)s.size(); i++ ) {
cout << ans[i] << " " << i << " \n";
}
//cout << endl;
}
int main() {
IOS;
int _ = 1;
//cin>>_;
while( _-- )
solve();
}
/*
potato
1 1
1 2
3 3
3 4
3 5
5 6
*/