FFT
/*
FFT模板 以此题为例
这道题就是给两个序列a,b
求b序列中有多少个数可以表示为a序列中的一个数,或者两个数之和(可以同一个数)
可以构造两个一样的多项式,a序列有相应的数的项的系数为1,否则为0
因为还有1个数的情况,所以看作a中有一个0。
这样用多项式乘法,就可以得出所有的和。
*/
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 8e5 + 10; //答案多项式次数的两倍 也是limit的理论最大值
const double pi = acos(-1.0);
struct Complex
{
double x, y;
}a[N], b[N], c[N];
int ans[N], r[N], n, m, l, limit;
Complex operator + (Complex a, Complex b) { return Complex{a.x + b.x, a.y + b.y}; }
Complex operator - (Complex a, Complex b) { return Complex{a.x - b.x, a.y - b.y}; }
Complex operator * (Complex a, Complex b) { return Complex{a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }
void FFT(Complex *A, int type)
{
rep(i, 0, limit)
if(i < r[i])
swap(A[i], A[r[i]]);
for(int mid = 1; mid < limit; mid <<= 1)
{
Complex Wn{cos(pi / mid), type * sin(pi / mid)};
for(int R = mid << 1, j = 0; j < limit; j += R)
{
Complex w{1, 0};
for(int k = 0; k < mid; k++, w = w * Wn)
{
Complex x = A[j + k], y = w * A[j + mid + k];
A[j + k] = x + y;
A[j + mid + k] = x - y;
}
}
}
}
void solve()
{
for(limit = 1, l = 0; limit <= n + m; limit <<= 1, l++); //FFT只能处理2^t的多项式 limit表示最高次数 n + m即答案多项式次数
rep(i, 0, limit) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
FFT(a, 1); FFT(b, 1); //FFT 系数表示法转化为点值表示法
_for(i, 0, limit) c[i] = a[i] * b[i]; //点值表示法直接相乘
FFT(c, -1); //逆FFT 点值表示法转化为系数表示法 最后要多除个limit(下一行)
_for(i, 0, n + m) ans[i] = (int)(c[i].x / limit + 0.5);
}
int main()
{
while(~scanf("%d", &n))
{
memset(a, 0, sizeof a); //多组数据只用清空a b两个多项式的数组
memset(b, 0, sizeof b);
a[0].x = b[0].x = 1;
_for(i, 1, n)
{
int x; scanf("%d", &x);
a[x].x = b[x].x = 1; //a[i].x表示x^i项的系数
}
m = n = 2e5; //n表示a多项式的次数 m是b多项式的次数
solve();
int cnt = 0;
scanf("%d", &m);
while(m--)
{
int x; scanf("%d", &x);
cnt += (ans[x] > 0);
}
printf("%d\n", cnt);
}
return 0;
}
splay
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 1e5 + 10;
int rt, tot, fa[N], ch[N][2], val[N], cnt[N], sz[N];
struct Splay
{
void maintain(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + cnt[x]; } //重新计算x的sz值
bool get(int x) { return x == ch[fa[x]][1]; } //判断x是不是右儿子
void clear(int x) //销毁x节点
{
ch[x][0] = ch[x][1] = fa[x] = val[x] = sz[x] = cnt[x] = 0;
}
void rotate(int x) //旋转操作 包含左旋和右旋
{
int y = fa[x], z = fa[y], chk = get(x);
ch[y][chk] = ch[x][chk ^ 1];
if(ch[x][chk ^ 1]) fa[ch[x][chk ^ 1]] = y;
ch[x][chk ^ 1] = y;
fa[y] = x; fa[x] = z;
if(z) ch[z][y == ch[z][1]] = x;
maintain(y);
maintain(x);
}
void splay(int x) //将x旋转到根 每次访问x后都要splay
{
for(int f = fa[x]; f = fa[x]; rotate(x))
if(fa[f]) rotate(get(x) == get(f) ? f : x);
rt = x;
}
void ins(int k) //插入操作
{
if(!rt)
{
val[++tot] = k;
cnt[tot]++;
rt = tot;
maintain(rt);
return;
}
int cur = rt, f = 0;
while(1)
{
if(val[cur] == k)
{
cnt[cur]++;
maintain(cur);
maintain(f);
splay(cur);
break;
}
f = cur;
cur = ch[cur][val[cur] < k];
if(!cur)
{
val[++tot] = k;
cnt[tot]++;
fa[tot] = f;
ch[f][val[f] < k] = tot;
maintain(tot);
maintain(f);
splay(tot);
break;
}
}
}
int rk(int k) //获取k的排名
{
int res = 0, cur = rt;
while(1)
{
if(k < val[cur]) cur = ch[cur][0];
else
{
res += sz[ch[cur][0]];
if(k == val[cur])
{
splay(cur);
return res + 1;
}
res += cnt[cur];
cur = ch[cur][1];
}
}
}
int kth(int k) //排名第k的是哪一个数
{
int cur = rt;
while(1)
{
if(ch[cur][0] && k <= sz[ch[cur][0]]) cur = ch[cur][0];
else
{
k -= cnt[cur] + sz[ch[cur][0]];
if(k <= 0)
{
splay(cur);
return val[cur];
}
cur = ch[cur][1];
}
}
}
int pre() //返回k的前驱 注意函数外要先插入再删除
{
int cur = ch[rt][0];
if (!cur) return cur;
while(ch[cur][1]) cur = ch[cur][1];
splay(cur);
return cur;
}
int nxt() //返回k的后继 注意函数外要先插入再删除
{
int cur = ch[rt][1];
if (!cur) return cur;
while(ch[cur][0]) cur = ch[cur][0];
splay(cur);
return cur;
}
void del(int k) //删除k 只删一个
{
rk(k);
if(cnt[rt] > 1)
{
cnt[rt]--;
maintain(rt);
return;
}
if(!ch[rt][0] && !ch[rt][1])
{
clear(rt);
rt = 0;
return;
}
if(!ch[rt][0])
{
int cur = rt;
rt = ch[rt][1];
fa[rt] = 0;
clear(cur);
return;
}
if(!ch[rt][1])
{
int cur = rt;
rt = ch[rt][0];
fa[rt] = 0;
clear(cur);
return;
}
int cur = rt;
int x = pre();
fa[ch[cur][1]] = x;
ch[x][1] = ch[cur][1];
clear(cur);
maintain(rt);
}
}tree;
int main()
{
int n;
scanf("%d", &n);
_for(i, 1, n)
{
int op, x;
scanf("%d%d", &op, &x);
if (op == 1) tree.ins(x);
if (op == 2) tree.del(x);
if (op == 3) printf("%d\n", tree.rk(x));
if (op == 4) printf("%d\n", tree.kth(x));
if (op == 5) tree.ins(x), printf("%d\n", val[tree.pre()]), tree.del(x);
if (op == 6) tree.ins(x), printf("%d\n", val[tree.nxt()]), tree.del(x);
}
return 0;
}
文艺平衡树(rope)
rope这个STL可以用来解决一些平衡树的问题
它可以时间区间插入,删除,替换,都是logn
这道题而言就建立两个rope,一个正的一个反的,替换的时候就替换对应区间就可以了
用取子串相加这个操作
#include <bits/stdc++.h>
#include <ext/rope>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
using namespace __gnu_cxx;
rope<int> a, b;
int n, m;
void solve(int l, int r)
{
int ll = n - 1 - r, rr = n - 1 - l;
rope<int> t = a;
a = a.substr(a.begin(), a.begin() + l) + b.substr(b.begin() + ll, b.begin() + rr + 1) + a.substr(a.begin() + r + 1, a.end());
b = b.substr(b.begin(), b.begin() + ll) + t.substr(t.begin() + l, t.begin() + r + 1) + b.substr(b.begin() + rr + 1, b.end());
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
a.push_back(i);
b.push_back(n - i + 1);
}
while(m--)
{
int l, r;
scanf("%d%d", &l, &r);
solve(l - 1, r - 1);
}
for(int x: a) printf("%d ", x);
return 0;
}
点分治
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 1e4 + 10;
const int M = 1e7 + 10;
vector<pair<int, int>> g[N];
int q[N], ans[N], maxp[N], vis[N], siz[N], rt, sum, n, m;
vector<int> ve;
bool judge[M];
void getrt(int u, int fa)
{
siz[u] = 1; maxp[u] = 0; //注意这里初始化maxp
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(v == fa || vis[v]) continue;
getrt(v, u);
siz[u] += siz[v];
maxp[u] = max(maxp[u], siz[v]);
}
maxp[u] = max(maxp[u], sum - siz[u]);
if(maxp[u] < maxp[rt]) rt = u;
}
void getdis(int u, int fa, int d)
{
if(d <= 1e7) ve.push_back(d); //询问到1e7 总和会到1e8
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(vis[v] || v == fa) continue;
getdis(v, u, d + w);
}
}
void solve(int u)
{
vector<int> tmp; //防止memset
for(auto x: g[u])
{
int v = x.first, w = x.second;
if(vis[v]) continue;
ve.clear();
getdis(v, u, w);
for(int x: ve)
_for(i, 1, m)
if(q[i] >= x)
ans[i] |= judge[q[i] - x];
for(int x: ve)
{
judge[x] = true;
tmp.push_back(x);
}
}
for(int x: tmp) judge[x] = false;
}
void divide(int u)
{
vis[u] = 1; //删除u
solve(u); //处理经过u的路径
for(auto x: g[u])
{
int v = x.first;
if(vis[v]) continue;
maxp[rt = 0] = sum = siz[v]; //这里初始化sum是siz[v]
getrt(v, 0);
getrt(rt, 0);
divide(rt);
}
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n - 1)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back({v, w});
g[v].push_back({u, w});
}
_for(i, 1, m) scanf("%d", &q[i]);
judge[0] = true; //注意要初始化0
maxp[rt = 0] = sum = n; //初始化rt
getrt(1, 0); //找重心
getrt(rt, 0); //更新正确的siz数组
divide(rt);
_for(i, 1, m) puts(ans[i] ? "AYE" : "NAY");
return 0;
}
SAM
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e6 + 10; //字符串总长两倍
int t[N][26], len[N], fa[N], c[N], k[N]; //t为自动机上的边,即fail,fa为parent tree上的边
int siz[N], cnt = 1, last = 1, n; //len表示节点i的类中最长串的长度
vector<int> endpos[N];
char s[N];
ll ans;
void add(int c, int pos) //题目不同更新u,r两处 不只有小写字母也要改
{
int p = last;
int u = last = ++cnt; //u是新建的节点
len[u] = len[p] + 1;
for(; p && !t[p][c]; p = fa[p]) t[p][c] = u;
if(!p) fa[u] = 1;
else
{
int q = t[p][c];
if(len[q] == len[p] + 1) fa[u] = q;
else
{
int r = ++cnt; //r是多建的虚空点
_for(i, 0, 25) t[r][i] = t[q][i]; //有些题不只是小写字母 注意
fa[r] = fa[q];
len[r] = len[p] + 1;
fa[q] = fa[u] = r;
for(; p && t[p][c] == q; p = fa[p])
t[p][c] = r;
//更新r
}
}
//更新u
siz[u] = 1; //注意siz的初始化只有siz[u] = 1 不要写siz[q] = 1
endpos[u].push_back(pos); //siz表示有多少个终止位置,也是子串出现次数
}
int main()
{
scanf("%s", s + 1);
n = strlen(s + 1);
_for(i, 1, n) add(s[i] - 'a', i);
_for(i, 1, cnt) c[len[i]]++; //根据len桶排序 也可以dfs求siz数组
_for(i, 1, cnt) c[i] += c[i - 1];
_for(i, 1, cnt) k[c[len[i]]--] = i;
for(int i = cnt; i >= 1; i--)
{
int id = k[i];
siz[fa[id]] += siz[id];
for(int x: endpos[id]) endpos[fa[id]].push_back(x);
}
_for(i, 1, n)
if(siz[i] > 1)
ans = max(ans, 1LL * siz[i] * len[i]);
printf("%lld\n", ans);
return 0;
}
单调栈
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 3e6 + 10;
int a[N], ans[N], s[N], top, n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
for(int i = n; i >= 1; i--)
{
while(top && a[s[top]] <= a[i]) top--; //注意这里是while 不是 if 维持单调性
ans[i] = top ? s[top] : 0; //通过当前栈顶记录答案
s[++top] = i; //将当前元素压入栈
}
_for(i, 1, n) printf("%d ", ans[i]);
return 0;
}
珂朵莉树模板
应用条件
1.有区间赋值操作
2.数据随机 否则会被构造数据卡成暴力
本质上就是应用set,维护三元组(l, r, v)
因为有区间赋值操作导致区间个数小,保证复杂度
struct node
{
int l, r;
mutable int v; //注意关键字mutable
bool operator < (const node& rhs) const
{
return l < rhs.l;
}
};
set<node> s;
int n;
auto split(int x)
{
auto it = s.lower_bound({x, 0, 0});
if (it != s.end() && it->l == x) return it;
--it;
int l = it->l, r = it->r, v = it->v;
s.erase(it);
s.insert({l, x - 1, v});
return s.insert({x, r, v}).first;
}
void assign(int l, int r, int v)
{
auto itr = split(r + 1), itl = split(l); //注意先r后l
s.erase(itl, itr);
s.insert({l, r, v});
}
void add(int l, int r, int x)
{
auto itr = split(r + 1), itl = split(l);
for(auto it = itl; it != itr; it++)
{
//做某种操作
it->v += x;
}
}
ST表
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 3e5 + 10;
int stgcd[N][21], stmin[N][21], Log[N], n;
int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }
int ask_gcd(int l, int r)
{
int k = Log[r - l + 1]; //长度位2 ^ k
return gcd(stgcd[l][k], stgcd[r - (1 << k) + 1][k]);
}
int ask_min(int l, int r)
{
int k = Log[r - l + 1];
return min(stmin[l][k], stmin[r - (1 << k) + 1][k]);
}
bool check(int key)
{
_for(i, 1, n)
{
int j = i + key;
if(j > n) break;
int g = ask_gcd(i, j), mi = ask_min(i, j);
if(g == mi) return true;
}
return false;
}
void init()
{
Log[1] = 0;
_for(i, 2, n) Log[i] = Log[i / 2] + 1;
_for(j, 1, 20) //注意先枚举j后枚举i 因为每次更新要用到j - 1
for(int i = 1; i + (1 << j) - 1 <= n; i++) //注意f[i][j]不要超范围
{
stgcd[i][j] = gcd(stgcd[i][j - 1], stgcd[i + (1 << (j - 1))][j - 1]);
stmin[i][j] = min(stmin[i][j - 1], stmin[i + (1 << (j - 1))][j - 1]);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%d", &stgcd[i][0]);
stmin[i][0] = stgcd[i][0];
}
init();
int l = 0, r = n + 1;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) l = m;
else r = m;
}
vector<int> ans;
_for(i, 1, n)
{
int j = i + l;
if(j > n) break;
int g = ask_gcd(i, j), mi = ask_min(i, j);
if(g == mi) ans.push_back(i);
}
printf("%d %d\n", ans.size(), l);
for(int x: ans) printf("%d ", x);
return 0;
}
poj3261(后缀数组 + 二分)
同样是二分一个答案,然后根据height来分组
一个组内,如果长度有t次,说明有一个串重复了t次,且长度大于等于ans
这个串就是每一个后缀的前ans个字符 可以画图理解一下
所以只要判断组内的后缀个数大于等于k即可
#include <cstdio>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e4 + 10;
const int M = 1e6 + 10;
int s[N], sa[N], rak[N], tp[N], tax[M], height[N], n, m, K; //注意M
void Qsort()
{
_for(i, 0, m) tax[i] = 0;
_for(i, 1, n) tax[rak[i]]++;
_for(i, 1, m) tax[i] += tax[i - 1];
for(int i = n; i >= 1; i--) sa[tax[rak[tp[i]]]--] = tp[i];
}
void SuffixSort()
{
m = 1e6; //s[i]的元素范围在0~m内
_for(i, 1, n) rak[i] = s[i], tp[i] = i;
Qsort();
for(int w = 1, p = 0; p < n; m = p, w <<= 1)
{
p = 0;
_for(i, 1, w) tp[++p] = n - i + 1;
_for(i, 1, n) if(sa[i] > w) tp[++p] = sa[i] - w;
Qsort(); swap(tp, rak);
rak[sa[1]] = p = 1;
_for(i, 2, n)
rak[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w]) ? p : ++p;
}
int k = 0;
_for(i, 1, n)
{
if(k) k--;
int j = sa[rak[i] - 1];
while(i + k <= n && j + k <= n && s[i + k] == s[j + k]) k++;
height[rak[i]] = k;
}
}
bool check(int key)
{
int cnt = 1;
_for(i, 2, n)
{
if(height[i] >= key) cnt++;
else cnt = 1;
if(cnt >= K) return true;
}
return false;
}
int main()
{
scanf("%d%d", &n, &K);
_for(i, 1, n) scanf("%d", &s[i]);
SuffixSort();
int l = 1, r = n;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) l = m;
else r = m;
}
printf("%d\n", l);
return 0;
}
回文自动机(PAM)
两颗trie树,一颗代表偶数长度回文串,一颗代表奇数长度回文串
一个节点代表一个回文串
fail指针代表当前回文串的最长回文后缀
代码量挺小,可以用来求本质不同的回文串数目,即新建的节点数目cnt-1
可以用来求每个节点出现的次数,每次先sum[last]++
然后再倒序sum[fail[j]] += sum[j] 这样就可以求出
这道题是求每个节点为结尾的回文串数目
每次新建节点的时候,num[cnt] = num[fail[cnt]] + 1
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e5 + 10;
int ch[N][26], len[N], fail[N], sum[N], n;
char s[N];
int main()
{
scanf("%s", s + 1);
n = strlen(s + 1);
s[0] = '#';
fail[0] = 1; len[1] = -1;
int last = 0, cnt = 1, pre;
_for(i, 1, n)
{
if(i > 1) s[i] = (s[i] - 97 + pre) % 26 + 97;
while(s[i] != s[i - len[last] - 1]) last = fail[last];
if(!ch[last][s[i] - 'a'])
{
len[++cnt] = len[last] + 2;
int j = fail[last];
while(s[i] != s[i - len[j] - 1]) j = fail[j];
fail[cnt] = ch[j][s[i] - 'a'];
sum[cnt] = sum[fail[cnt]] + 1;
ch[last][s[i] - 'a'] = cnt;
}
last = ch[last][s[i] - 'a'];
printf("%d ", pre = sum[last]);
}
return 0;
}
CF165E Compatible Numbers(高维前缀和)
对于一个询问,显然有1的地方都不能有1
所以也就是当前的a[i]取反,问有没有元素是它的子集
二进制,子集,可以用高维前缀和
用高维前缀和的思路,处理出每一个数的子集中存不存在题目给的数,存在的话用任意一个数就可以。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int f[1 << 22], a[N], w = 22, n;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%d", &a[i]);
f[a[i]] = a[i];
}
rep(i, 0, w)
rep(j, 0, 1 << w)
if(j & (1 << i) && f[j ^ (1 << i)]) f[j] = f[j ^ (1 << i)];
_for(i, 1, n)
{
int cur = ((1 << w) - 1) ^ a[i];
printf("%d ", f[cur] ? f[cur] : -1);
}
return 0;
}
CF1092F Tree with Maximum Cost(换根dp)
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
vector<int> g[N];
ll sum[N], d[N], a[N], ans, cur;
int n;
void dfs(int u, int fa)
{
sum[u] = a[u];
for(int v: g[u])
{
if(v == fa) continue;
d[v] = d[u] + 1;
dfs(v, u);
sum[u] += sum[v];
}
}
void dfs2(int u, int fa, ll val)
{
ans = max(ans, val);
for(int v: g[u])
{
if(v == fa) continue;
dfs2(v, u, val - sum[v] + sum[1] - sum[v]);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%lld", &a[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
_for(i, 1, n) cur += a[i] * d[i];
dfs2(1, 0, cur);
printf("%lld\n", ans);
return 0;
}