树上倍增求LCA
const int N = 1e5+5;
int lca[N][20];
int dep[N];
void dfs(int now,int f,int depth){///初始时dfs(根节点,0,1);
lca[now][0] = f;
dep[now] = depth;
for (int i=head[now];i!=0;i=edge[i].last){//链式前向星
int v = edge[i].to;
if (v==f) continue;
dfs(v,now,depth+1);
}
}
void getst(int n){
for (int j=1;(1<<j)<=n;j++)
for (int i=1;i<=n;i++)//这里千万别写成i+(1<<j)-1<=n,因为这不是维护区间
lca[i][j] = lca[lca[i][j-1]][j-1];
}
int getlca(int x,int y){
if (dep[x]<dep[y]) swap(x,y);
int differ = dep[x]-dep[y];
for (int i=0;(1<<i)<=differ;i++){
if ((1<<i)&differ){
x = lca[x][i];
}
}
if (x==y) return x;//忘了这行的话答案去下面传父亲就不对了
for (int i=18;i>=0;i--){
if (lca[x][i]!=lca[y][i]) x = lca[x][i],y = lca[y][i];
}
return lca[x][0];
}
最小表示法
///求所有循环同构中字典序最小或者最大
int getmin(int len)
{
int i=0,j=1,k=0;
while (i<len && j<len && k<len){
int t = s[(i+k)%len] - s[(j+k)%len];
if (!t) k++;
else {
if (t<0) j += k+1;
else i += k+1;
if (i==j) j++;
k = 0;
}
}
return i<j?i:j;
}
int getmax(int len)
{
int i=0,j=1,k=0;
while (i<len && j<len && k<len){
int t = s[(i+k)%len] - s[(j+k)%len];
if (!t) k++;
else {
if (t>0) j += k+1;
else i += k+1;
if (i==j) j++;
k = 0;
}
}
return i<j?i:j;
}
Manacher
精简版
void manacher(char* s){
//init
ss[0] = '@';
int len = strlen(s);
for1(i,1,len){
ss[2*i-1] = '#';
ss[2*i] = s[i-1];
}
ss[2*len+1]='#';
ss[2*len+2]='$';
ss[2*len+3] = '\0';
len = len*2+4;
int ans = 0;
int pos=1,maxp=1,j;
pal[0] = pal[1] = 1;
for (int i=2;i<len;i++){
if (pal[2*pos-i]<maxp-i+1) pal[i] = pal[2*pos-i];
else {
j = maxp-i+1;
//if (j<=1) j = 1;
while (i-j>=0 && i+j<len && ss[i+j]==ss[i-j]) j++;
pal[i] = j,pos = i,maxp = max(maxp,i+pal[i]-1);
ans = max(ans,pal[i]-1);
}
}
printf("%d\n",ans);
}
复杂版
const int maxn=1000010;
char str[maxn];//原字符串
char tmp[maxn<<1];//转换后的字符串
int Len[maxn<<1];
//转换原始串
int INIT(char *st)
{
int i,len=strlen(st);
tmp[0]='@';///开头加一个不等于#也不等于字符串的字符,这样就不用判断左边越界了,那么右边万一比到n+1怎么办呢?有\0吗?不,\0在n处,解决办法看16行
for(i=1;i<=2*len;i+=2)
{
tmp[i]='#';
tmp[i+1]=st[i/2];
}
tmp[2*len+1]='#';
tmp[2*len+2]='$';///结尾搞一个不是@也不是#的字符
tmp[2*len+3]=0;
return 2*len+1;//返回转换字符串的长度
}
//Manacher算法计算过程
int MANACHER(char *st,int len)
{
int mx=0,ans=0,po=0;//mx即为当前计算回文串最右边字符的最大值
for(int i=1;i<=len;i++)
{
if(mx>i)
Len[i]=min(mx-i,Len[2*po-i]);//在Len[j]和mx-i中取个小
else
Len[i]=1;//如果i>=mx,要从头开始匹配
while(st[i-Len[i]]==st[i+Len[i]])
Len[i]++;
if(Len[i]+i>mx)//若新计算的回文串右端点位置大于mx,要更新po和mx的值
{
mx=Len[i]+i;
po=i;
}
ans=max(ans,Len[i]);
}
return ans-1;//返回Len[i]中的最大值-1即为原串的最长回文子串额长度
}
Len[st+ed]-1>=ed-st+1判断区间是否回文
EXKMP
精简版
void exkmp(char *a,char *b){
//getnt
int pos=0,maxp=0,j;
int lena = strlen(a),lenb = strlen(b);
nxt[0] = lenb;
for (int i=1;i<lenb;i++){
if (nxt[i-pos]<maxp-i+1) nxt[i] = nxt[i-pos];
else {
j = maxp-i+1;//if (j<0) j = 0;
while (i+j<lenb && b[i+j]==b[j]) j++;
nxt[i] = j,pos = i,maxp = max(maxp,max(i+nxt[i]-1,i));
}
}
//getex
pos = -1,maxp = -1;
for (int i=0;i<lena;i++){
if (nxt[i-pos]<maxp-i+1) ex[i] = nxt[i-pos];
else {
j = maxp-i+1;//if (j<0) j = 0;
while (i+j<lena && j<lenb && a[i+j]==b[j]) j++;
ex[i] = j,pos = i,maxp = max(maxp,max(i+ex[i]-1,i));
}
}
}
不精简版
const int maxn=100010; //字符串长度最大值
int nt[maxn],ex[maxn]; //ex数组即为extend数组
///预处理计算next数组
void GETNEXT(char *str)
{
int i=0,j,po,len=strlen(str);
nt[0]=len;///用自己作为后缀与自己匹配
while(i+1<len && str[i]==str[i+1]) i++;///暴力求next[1]
nt[1]=i;
po=1;///从此点出发next数组延伸位置最远
for(i=2;i<len;i++)
{
if(nt[i-po]< nt[po]+po-i )///第一种情况,可以直接得到next[i]的值
nt[i]=nt[i-po];
else///第二种情况,要继续匹配才能得到next[i]的值
{
j=nt[po]+po-i;
if(j<0)j=0; ///小于0表示没有已知相同部分,重新开始匹配
while(i+j<len&&str[j]==str[j+i])
j++;
nt[i]=j;
po=i;///更新po的位置
}
}
}
///计算extend数组
void EXKMP(char *s1,char *s2)
{
int i=0,j,po,len=strlen(s1),l2=strlen(s2);
GETNEXT(s2);
while(i<l2&&i<len&&s1[i]==s2[i])
i++;
ex[0]=i;
po=0;
for(i=1;i<len;i++)
{
if(nt[i-po]<ex[po]+po-i)
ex[i]=nt[i-po];
else
{
j=ex[po]+po-i;
if(j<0)j=0;
while(i+j<len&&j<l2&&s1[j+i]==s2[j])
j++;
ex[i]=j;
po=i;
}
}
}
KMP
#include<bits/stdc++.h>
const int N = 1e6+5;
using namespace std;
int nt[N];
char s[N];
char ss[N];
int KMP() ///求大串中有几个子串
{
int len=strlen(s);
int t=strlen(ss);
nt[0]=-1;
int cnt=0;
///构造next数组
for (int i=0,j=-1; i<t; ){
if (j==-1 || ss[i]==ss[j]){
i++;
j++;
nt[i]=j;
}
else j=nt[j];
}
for (int i=0;i<t;i++) printf("nt[%d]=%d ",i,nt[i]); printf("\n");
///开始遍历大串找其中的小串
for (int i=0,j=0; i<len; ){
if (j==-1 || ss[j]==s[i]){
i++;
j++;
}
else j=nt[j];
if (j==t){
cnt++;
j=0;
}
}
return cnt;
}
int main()
{
while (cin>>s){
cin>>ss;
cout<<KMP()<<endl;
}
}
字典树---普通版本
int trie[maxnode][sigma_size];///maxnode表示节点数,maxnode=单词数*每个单词最大长度(最坏情况每个单词组合都不一样)
///sigma_size 表示一个节点最多延伸出多少各节点,也就是字符种类
/// trie[i][j] 的值表示 i节点指向j这个数字对应字母 的节点的位置
bool val[maxnode]; ///为真表示当前节点是一个单词的结束
int sz;
int idx(char ch) { return ch-'a';}//将字母转换成对应数字,如果还有大写,标点的话要更麻烦些
void init(int x){
val[x] = false;
memset(trie[x],0,sizeof trie[x]);
}
void insert(char *s){///插入
int u = 0;
for (int i=0;s[i];i++){
int v = idx(s[i]);
if (!trie[u][v]){
init(sz);///初始化多组输入时上一次残留的数据
trie[u][v] = sz++;
}
u = trie[u][v];
}
val[u] = true;
}
bool query(char *s){
int u = 0;
for (int i=0;s[i];i++){
int v = idx(s[i]);
if (!trie[u][v]) return false; ///这个节点还没有开辟
u = trie[u][v];
}
return true;
}
领接表存字典树
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N = 4000*1001 + 5;
struct node
{
int son;
int right;
int sum;
char ch;
}trie[N];
int id;
ll ans;
void init()
{
ans = 0;
id = 1;
trie[0].right = 0;
trie[0].son = 0;
trie[0].sum = 0;
}
void insert(char *s)
{
int u = 0,j;
int len = strlen(s);
for (int i=0;i<=len;i++){
bool flag = false;
for (j=trie[u].son;j!=0;j=trie[j].right){
if (s[i]==trie[j].ch){
flag = true;
break;
}
}
if (!flag){
j = id++;
trie[j].right = trie[u].son;
trie[u].son = j;
trie[j].ch = s[i];
trie[j].son = 0;
trie[j].sum = 0;
}
ans += (trie[u].sum+trie[j].sum);
if (i==len) trie[j].sum++;
trie[u].sum++;
u = j;
}
}
int main()
{
int n;
char in[1010];
for (int kca=1;scanf("%d",&n),n;kca++){
init();
while (n--) scanf("%s",in),insert(in);
printf("Case %d: %lld\n",kca,ans);
}
}
后缀数组求两个串LCS----最长公共子串
#include<cstdio>
#include<algorithm>
#include<cstring>
#define debug(x) printf("----Line%s----\n",#x)
using namespace std;
const int N = 1e5+5;
int wa[N<<1],wb[N<<1],sa[N<<1],rnk[N<<1],cnt[N<<1],height[N<<1];
char s[N<<1];
void build(int n,int m)
{
int *x=wa,*y=wb,p,i,j;
for (i=0;i<m;i++) cnt[i] = 0;
for (i=0;i<n;i++) cnt[x[i]=s[i]]++;
for (i=1;i<m;i++) cnt[i] += cnt[i-1];
for (i=n-1;i>=0;i--) sa[--cnt[x[i]]] = i;
for (j=1,p=1;p<n;j<<=1,m=p){
for (p=0,i=n-j;i<n;i++) y[p++] = i;
for (i=0;i<n;i++) if (sa[i]>=j) y[p++] = sa[i]-j;
for (i=0;i<m;i++) cnt[i] = 0;
for (i=0;i<n;i++) cnt[x[y[i]]]++;
for (i=1;i<m;i++) cnt[i] += cnt[i-1];
for (i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]] = y[i];
swap(x,y);
x[sa[0]] = 0;
p = 1;
for (i=1;i<n;i++)
x[sa[i]] = ( y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j] )? p-1:p++;
}
}
void getheight(int n)
{
int k=0;
for (int i=0;i<n;i++) rnk[sa[i]] = i;
for (int i=0;i<n;i++){
if (rnk[i]==0){height[rnk[i]]=k=0;continue;}
if (k) k--;
int j = sa[rnk[i]-1];
while (i+k<n && j+k<n && s[i+k]==s[j+k]) k++;
height[rnk[i]] = k;
}
}
int main()
{
int n;
while (~scanf("%s",s)){
int len = strlen(s);
scanf("%s",s+len+1);
n = len + strlen(s+len+1) + 1;///+1很关键,虽然这题数据太水不+1也会AC
build(n,200);//debug(1);
getheight(n);//debug(2);
int ans = 0;
for (int i=1;i<n;i++)
if ( (sa[i]<len && sa[i-1]>len) || (sa[i]>len && sa[i-1]<len) )
ans = max(height[i],ans);
//debug(3);
printf("%d\n",ans);
}
return 0;
}
后缀数组查询两段区间LCP,ST维护height
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define debug(x) printf("----line %s----\n",#x)
using namespace std;
const int N = 2e5 + 5;
int sa[N],wa[N],wb[N],rnk[N],cnt[N],height[N];
char s[N];
int st[N][20];
void buildsa(int n,int m)///m是初始字符种类,可以偏大
{
int *x = wa,*y = wb,p,i,j;
for (int i=0;i<m;i++) cnt[i] = 0;
for (int i=0;i<n;i++) cnt[x[i]=s[i]]++;
for (int i=1;i<m;i++) cnt[i] += cnt[i-1];
for (int i=n-1;i>=0;i--) sa[--cnt[x[i]]] = i;
for (j=1,p=1;p<n;j<<=1,m=p){
for (p=0,i=n-j;i<n;i++) y[p++] = i;
for (int i=0;i<n;i++) if (sa[i]>=j) y[p++] = sa[i]-j;///得出根据第二关键字排名的
for (int i=0;i<m;i++) cnt[i] = 0;
for (int i=0;i<n;i++) cnt[x[y[i]]]++;
for (int i=1;i<m;i++) cnt[i] += cnt[i-1];
for (int i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]] = y[i];
swap(x,y);
x[sa[0]] = 0;
p = 1;
for (i=1;i<n;i++)
x[sa[i]] = ( y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j] )? p-1:p++;
}
}
void getheight(int n)
{
int k = 0;
for (int i=0;i<n;i++) rnk[sa[i]] = i;
for (int i=0;i<n;i++){
if (rnk[i]==0){height[rnk[i]] = k = 0;continue;}
if (k) k--;
int j = sa[rnk[i]-1];
while (i+k < n && j+k < n && s[i+k]==s[j+k]) k++;
height[rnk[i]] = k;
}
}
void ST(int n)///维护height的区间最小值
{
for (int i=1;i<=n;i++) st[i][0] = height[i];
for (int j=1;(1<<j)<=n;j++)
for (int i=1;i+(1<<j)-1<=n;i++)
st[i][j] = min(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
int query(int l,int r)
{
int k = log(r-l+1.0)/log(2.0);
return min(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
int n;
scanf("%d",&n);
scanf("%s",s);
buildsa(n+1,300);//debug(1);///s 0~n
getheight(n+1);//debug(2);///height 0~n
ST(n);//debug(3);///height 1~n
int q,a,b,c,d,ans;
scanf("%d",&q);
while (q--){
scanf("%d %d %d %d",&a,&b,&c,&d);
int l = rnk[a],r = rnk[c];
if (l>r) swap(l,r);
if (l==r){
ans = min(b-a+1,d-c+1);
}
else {
ans = min(min(b-a+1,d-c+1),query(l+1,r));
}
printf("%d\n",ans);
}
return 0;
}
大数运算
const int power = 4; ///每次运算的位数为10的power次方,在这里定义为了方便程序实现
const int base = 10000; ///10的power次方。
const int MAXL = 200; ///MAXL*POWER = 表示的数的范围
char a[MAXL], b[MAXL];
struct num
{
int a[MAXL];///如果POWER>4,不修改为修改为LL乘法会爆
num() { memset(a, 0, sizeof(a)); } ///初始化
num(char *s) ///将一个字符串初始化为高精度数
{
memset(a, 0, sizeof(a));
int len = strlen(s);
a[0] = (len+power-1) / power; ///数的长度
for (int i=0, t=0, w; i < len ;w *= 10, ++i)
{
if (i % power == 0) { w = 1, ++t; }
a[t] += w * (s[i]-'0');
}
}
void add(int k) { if (k || a[0]) a[ ++a[0] ] = k; } ///在末尾添加一个数,除法的时候要用到,不是当前数+k!!
void re() { reverse(a+1, a+a[0]+1); } ///把数反过来,除法的时候要用到
void print() ///打印此高精度数
{
printf("%d", a[ a[0] ]);///先打印最高位,为了压位 或者 该高精度数为0 考虑
for (int i = a[0]-1;i > 0;--i)
printf("%0*d", power, a[i]);///这里"%0*d", power的意思是,必须输出power位,不够则前面用0补足
printf("\n");
}
} p,q,ans;
bool operator < (const num &p, const num &q) ///判断小于关系,除法的时候有用
{
if (p.a[0] < q.a[0]) return true;
if (p.a[0] > q.a[0]) return false;
for (int i = p.a[0];i > 0;--i)
{
if (p.a[i] != q.a[i]) return p.a[i] < q.a[i];
}
return false;
}
num operator + (const num &p, const num &q) ///加法,不用多说了吧,模拟一遍,很容易懂
{
num c;
c.a[0] = max(p.a[0], q.a[0]);
for (int i = 1;i <= c.a[0];++i)
{
c.a[i] += p.a[i] + q.a[i];
c.a[i+1] += c.a[i] / base;
c.a[i] %= base;
}
if (c.a[ c.a[0]+1 ]) ++c.a[0];
return c;
}
num operator - (const num &p, const num &q) ///减法,也不用多说,模拟一遍,很容易懂
{
num c = p;
for (int i = 1;i <= c.a[0];++i)
{
c.a[i] -= q.a[i];
if (c.a[i] < 0) { c.a[i] += base; --c.a[i+1]; }
}
while (c.a[0] > 0 && !c.a[ c.a[0] ]) --c.a[0];
///我的习惯是如果该数为0,那么他的长度也是0,方便比较大小和在末尾添加数时的判断。
return c;
}
num operator * (const num &p, const num &q)
///乘法,还是模拟一遍。。其实高精度就是模拟人工四则运算!
{
num c;
c.a[0] = p.a[0]+q.a[0]-1;
for (int i = 1;i <= p.a[0];++i)
for (int j = 1;j <= q.a[0];++j)
{
c.a[i+j-1] += p.a[i]*q.a[j];
c.a[i+j] += c.a[i+j-1] / base;
c.a[i+j-1] %= base;
}
if (c.a[ c.a[0]+1 ]) ++c.a[0];
return c;
}
num operator / (const num &p, const num &q) ///除法,这里我稍微讲解一下
{
num x, y;
for (int i = p.a[0];i >= 1;--i) ///从最高位开始取数
{
y.add(p.a[i]); ///把数添到末尾(最低位),这时候是高位在前,低位在后
y.re(); ///把数反过来,变为统一的存储方式:低位在前,高位在后
while ( !(y < q) ) ///大于等于除数的时候,如果小于的话,其实答案上的该位就是初始的“0”
y = y - q, ++x.a[i]; ///看能减几个除数,减几次,答案上该位就加几次。
y.re(); ///将数反过来,为下一次添数做准备
}
x.a[0] = p.a[0];
while (x.a[0] > 0 && !x.a[x.a[0]]) --x.a[0];
return x;
}
线性求1~n的逆元
p = k*i+r
k*i+r === 0 mod p,然后两边同乘以r^-1*i^-1
k*r^-1 + i^-1 === 0 mod p
inv[i] = -k * (1/r) mod p
inv[i] = (p-[p/i])*inv[p%i]%p
inv[1] = 1;
for (ll i=2;i<=n;i++)
inv[i] = (mod-mod/i)*inv[mod%i]%mod;
excrt(扩展中国剩余定理)
第28行有修改,之前可能爆int了
先理清几个东西:
因为ax + by = c有解--->c是gcd(a,b)的整数倍--->求解出ax+by=gcd(a,b)的x,y都乘以c/gcd(a,b)即可
所以ax+by=1有解--->互质--->gcd(a,b) = 1
求解一次线性同余方程组
x === a1(mod m1)
x === a2(mod m2)
......
x = k1*m1+a1 = k2*m2+a2
先求解 m1*k1 - m2*k2 = a2-a1
求解出特解k1,得到 x = k1*m1+a1为满足第一第二个式子的特解,此时第一第二个式子通解为XX = x + LCM(m1,m2)*k
即类似于x = ki*mi+ai的形式,这个通解的式子继续和下一项重复上述步奏
void exgcd(ll a,ll b,ll &x,ll &y)
{
if (b==0){x = 1;y = 0;}
else{
exgcd(b,a%b,y,x);
y = y - a/b*x;
}
}
ll gcd(ll a,ll b)
{
return b==0? a:gcd(b,a%b);
}
int main()
{
ll a1,a2,r1,r2,x,y;
int n;
while (~scanf("%d",&n)){
scanf("%lld %lld",&a1,&r1);///x === a1 mod r1
bool f = 1;
for (int i=2;i<=n;i++){
scanf("%lld %lld",&a2,&r2);///a1*x + r1 = a2*y + r2
ll r = gcd(a1,-a2); ///a1*x - a2*y = r2-r1
if ((r2-r1)%r!=0){f = 0;continue;}///无解
exgcd(a1,-a2,x,y);///算出来的是a1*x-a2*y = gcd(a1,a2)的解
ll p = a2/gcd(a1,a2);
x *= (r2-r1)/r; x = (x%p+p)%p;///求得实际x
///如果x>a2,x = xx + a2
///a1*(xx+a2) + a2*y =c 有解,那么a1*xx + a2*(y+a1)=c有解
///对于ax+by=c,如果有整数解x,y && x大于b,一定存在x小于b的整数解
/// X = (a1*x+r1) + LCM(a1,a2)*k
/// X = ri + ai*x
r1 = a1*x+r1;
a1 = a1/gcd(a1,a2)*a2;
r1 %= a1;///这一步没毛病
}
if (!f) puts("-1");
else printf("%lld\n",r1);
}
return 0;
}
回文树
来自:https://blog.youkuaiyun.com/u013368721/article/details/42100363
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+5;
const int ALP = 26;
struct PAM{ // 每个节点代表一个回文串
int next[maxn][ALP]; // next指针,参照Trie树
int fail[maxn]; // fail失配后缀链接
int cnt[maxn]; // 此回文串出现个数
int num[maxn];
int len[maxn]; // 回文串长度
int s[maxn]; // 存放添加的字符
int last; //指向上一个字符所在的节点,方便下一次add
int n; // 已添加字符个数
int p; // 节点个数
int l[maxn],r[maxn];//i节点代表的回文串第一次出现最左左右字符是第几个(不是下标)
int newnode(int w){//新建一个节点,长度初始化为这个节点代表的回文串长度
for(int i=0;i<ALP;i++)
next[p][i] = 0;
cnt[p] = 0;
num[p] = 0;
len[p] = w;
return p++;
}
void init(){
p = 0;
newnode(0);
newnode(-1);
last = 0;
n = 0;
s[n] = -1; // 开头放一个字符集中没有的字符,减少特判
fail[0] = 1;
}
int get_fail(int x){ // 和KMP一样,失配后找一个尽量最长的
while(s[n-len[x]-1] != s[n]) x = fail[x];
return x;
}
void add(int c){
c -= 'a';
s[++n] = c;
int cur = get_fail(last);
if(!next[cur][c]){
int now = newnode(len[cur]+2);//len赋值在这
fail[now] = next[get_fail(fail[cur])][c];
next[cur][c] = now;///这行和上一行一换瞬间去世
///因为一号节点是特殊节点,本身没有意义,只是一个
///代表所有子节点都是一个字符的回文串的节点
///两行交换会使fail[一个字符的节点] = fail[1][对应这个字符] = 自己
///而很显然我们要的是fail[] = 0;(一个字符的最长回文后缀==0)
///因此按照这个顺序即可正确获得
num[now] = num[fail[now]] + 1;
l[now] = n-len[now]+1;
r[now] = n;
}
last = next[cur][c];
cnt[last]++;
}
void count(){
// 最后统计一遍每个节点出现个数
// 父亲累加儿子的cnt,类似SAM中parent树
// 满足parent拓扑关系
for(int i=p-1;i>=0;i--)
cnt[fail[i]] += cnt[i];
}
}pam;
回文树前段插入
const int maxn = 1e5+5;
const int ALP = 26;
struct PAM{ // 每个节点代表一个回文串
int next[maxn][ALP]; // next指针,参照Trie树
int fail[maxn]; // fail失配后缀链接
int num[maxn];
int len[maxn]; // 回文串长度
int s[maxn<<1]; // 存放添加的字符
int l_last,r_last; //指向上一个字符所在的节点,方便下一次add
int l,r; //r-l+1表示现在添加的字符数量
int p; // 节点个数
ll cnt;//记录当前回文串总数
int newnode(int w){//新建一个节点,长度初始化为这个节点代表的回文串长度
for(int i=0;i<ALP;i++)
next[p][i] = 0;
num[p] = 0;
len[p] = w;
return p++;
}
void init(){
cnt = 0;
p = 0;
newnode(0);
newnode(-1);
l_last = r_last = 0;
memset(s,-1,sizeof s);//这样就不用特判边界了
l = maxn;
r = maxn-1;///pam中的s数组开了两倍,l,r都在中间
fail[0] = 1;
}
int get_fail_l(int x){
while (s[l+len[x]+1] != s[l]) x = fail[x];
return x;
}
int get_fail_r(int x){
while (s[r-len[x]-1] != s[r]) x = fail[x];
return x;
}
void ladd(int c){
c -= 'a';
s[--l] = c;
int cur = get_fail_l(l_last);
if (!next[cur][c]){
int now = newnode(len[cur]+2);
fail[now] = next[get_fail_l(fail[cur])][c];///直接父节点开始匹配就变成了自己,所以从父节点的最长回文后缀开始匹配
next[cur][c] = now; ///从这里也能开出如果父节点最长回文后缀不存在,那么当前最长回文后缀就是最后一个字母
num[now] = num[fail[now]] + 1;
}
l_last = next[cur][c];
cnt += num[l_last];
if (len[l_last]==r-l+1) r_last = l_last;
}
void radd(int c){
c -= 'a';
s[++r] = c;
int cur = get_fail_r(r_last);
if (!next[cur][c]){
int now = newnode(len[cur]+2);
fail[now] = next[get_fail_r(fail[cur])][c];
next[cur][c] = now;
num[now] = num[fail[now]] + 1;
}
r_last = next[cur][c];
cnt += num[r_last];
if (len[r_last]==r-l+1) l_last = r_last;
}
}pam;
快读模板:
找不到原文放不了链接了。
#include<bits/stdc++.h>
using namespace std;
namespace FastI{
const int SIZE = 1 << 16;
char buf[SIZE], str[64];
int l = SIZE, r = SIZE;
int read(char *s) {
while (r) {
for (; l < r && buf[l] <= ' '; l++);
if (l < r) break;
l = 0, r = int(fread(buf, 1, SIZE, stdin));
}
int cur = 0;
while (r) {
for (; l < r && buf[l] > ' '; l++) s[cur++] = buf[l];
if (l < r) break;
l = 0, r = int(fread(buf, 1, SIZE, stdin));
}
s[cur] = '\0';
return cur;
}
template<typename type>
bool read(type &x, int len = 0, int cur = 0, bool flag = false) {
if (!(len = read(str))) return false;
if (str[cur] == '-') flag = true, cur++;
for (x = 0; cur < len; cur++) x = x * 10 + str[cur] - '0';
if (flag) x = -x;
return true;
}
template <typename type>
type read(int len = 0, int cur = 0, bool flag = false, type x = 0) {
if (!(len = read(str))) return false;
if (str[cur] == '-') flag = true, cur++;
for (x = 0; cur < len; cur++) x = x * 10 + str[cur] - '0';
return flag ? -x : x;
}
} using FastI::read;
int main() {
//freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
//freopen("C:/Users/DELL/Desktop/output.txt", "w", stdout);
int a[1000];
while (read(a[1])){printf("%d",a[1]+1);}
return 0;
}
AC自动机
const int maxnode = 1e6+5;//模式串数量*长度
const int ALP = 26;//字符种类数
struct AC_am
{
queue<int>que;
int sz;
int trie[maxnode][ALP];
int fail[maxnode];
int last[maxnode];
int val[maxnode];//储存当前节点信息,如是否为单词节点等等
int newnode(int x){
memset(trie[x],0,sizeof trie[x]);
val[x] = 0;
return sz++;
}
void init(){
newnode(sz = 0);
}
int idx(char ch){//实际字符串转化为字典树对应节点,根据题目做出具体改变
return ch-'a';
}
void insert(char *s){
int u = 0;
for (int i=0;s[i];i++){
int c = idx(s[i]);
if (!trie[u][c]){
trie[u][c] = newnode(sz);
}
u = trie[u][c];
}
val[u]++;
}
void build(){
fail[0] = 0;
for (int c=0;c<ALP;c++){
int v = trie[0][c];
if (v){
que.push(v);
fail[v] = 0;
last[v] = 0;
}
}
while (!que.empty()){
int u = que.front();que.pop();
for (int c=0;c<ALP;c++){
int v = trie[u][c];
if (!v){
trie[u][c] = trie[fail[u]][c];
continue;
}
int fa = fail[u];
fail[v] = trie[fa][c];
last[v] = val[fail[v]]? fail[v]:last[fail[v]];
que.push(v);
}
}
}
int count(int x){
int cnt = 0;
while(x){
cnt += val[x];
val[x] = 0;
x = last[x];
}
return cnt;
}
int find(char *s){
int ans = 0;
int u = 0;
for (int i=0;s[i];i++){
int c = idx(s[i]);
u = trie[u][c];
if (val[u]) ans += count(u);
else if (last[u]) ans += count(last[u]);
//printf("nowans=%d\n",ans);
}
return ans;
}
}ac;
矩阵快速幂:
矩阵构造方法:写出两层递推式子,递推式有几项就构造几×几
const int matsize = 30;///矩阵最大大小
ll temp[matsize][matsize],ans[matsize][matsize];
int ms;///实际矩阵大小,每次矩阵快速幂之前根据矩阵大小修改
void multy(ll a[][matsize],ll b[][matsize])
{
memset(temp,0,sizeof temp);
for (int i=0;i<ms;i++)
for (int j=0;j<ms;j++)
for (int k=0;k<ms;k++)
temp[i][j] += a[i][k]*b[k][j];
for (int i=0;i<ms;i++)
for (int j=0;j<ms;j++)
a[i][j] = temp[i][j];
}
void matrix_fast_power(ull a[][matsize],ll b)
{
memset(ans,0,sizeof ans);
for (int i=0;i<ms;i++) ans[i][i] = 1;
while (b){
if (b&1) multy(ans,a);
b>>=1;
multy(a,a);
}
for (int i=0;i<ms;i++)
for (int j=0;j<ms;j++)
a[i][j] = ans[i][j];
}
Floyd 算法:
作用:求任意两点最短路
时间:O(V^3)
#define for0(i,a,b) for (int i=a;i<b;i++)
memset(dp,INF,sizeof dp);
for0(i,0,n) dp[i][i] = 0;
int u,v,w;
for0(i,0,m){
scanf("%d %d %d",&u,&v,&w);
dp[u][v] = min(dp[u][v],w);
dp[v][u] = min(dp[v][u],w);
}
for0(k,0,n)
for0(i,0,n)
for0(j,0,n)
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]);
dp[i][j]表示两个点之间可以用1~k-1作为中途点时的最短路径,输入取最小值防止有权值不同的重边
判断有无负环检查是否有dp[i][i]<0,有负环的话就没有最短路径了
堆优化dijkstra 算法:
作用:求单源最短路
时间:O(E*logE)
const int N = 1e5+5;///点的数量
const int M = 5e5+5;///边的数量
struct Edge//储存边
{
int to;
int last;
int w;
}edge[M];
int head[N],id;
void add(int u,int v,int w)//建从u->v,权值为w的边
{
edge[id].to = v;
edge[id].w = w;
edge[id].last = head[u];
head[u] = id++;
}
void init()//建边前的初始化
{
memset(head,0,sizeof head);
id = 1;
}
struct node//储存点
{
int now;
int w;//到达now节点的这条边的权值
bool operator < (const node& a)const{//***比较方式要和自己想的反过来***
return w>a.w;
}
};
bool vis[N];//是否求出最短路径
void dijkstra()
{
memset(vis,0,sizeof vis);
priority_queue<node>que;
int root = 1;//单元最短路径的源点
que.push({root,0});
while (!que.empty()){
node now = que.top();que.pop();
if (vis[now.now]) continue;
vis[now.now] = true;
/*
当前点now记录了这个点到源点的最短距离,问题可以在这儿处理
*/
for (int i=head[now.now];i!=0;i=edge[i].last){
que.push({edge[i].to,edge[i].w+now.w});//***权值记得要加now.w***
}
}
}
不能处理有负权的边的问题,否则当前连到树上的点不一定是最短路径。
跑完dijkstra后相当于建了一棵包含所有节点的树,树上所有点到源点的距离最近。
Bellman_ford 算法
作用:求带负边的单源最短路/判负环
时间:O(V*E)
#define INF 0x3f3f3f3f
const int N = 1e4+5;
const int M = 5e4+5;
struct Edge
{
int to,last,w;
}edge[M];
int id,head[N];
void add(int u,int v,int w)
{
edge[id].to = v;
edge[id].w = w;
edge[id].last = head[u];
head[u] = id++;
}
void init()
{
id = 1;
memset(head,0,sizeof head);
}
int dis[N];
void Bellman_ford(int V)//V表示节点数
{
int root = 1;//若只是判断负环,随便取一个源点即可
memset(dis,INF,sizeof dis);
dis[root] = 1;
for (int k=1;k<V;k++)
for (int u=1;u<=V;u++)
for (int i=head[u];i!=0;i=edge[i].last){
int v = edge[i].to;
if (dis[v]>dis[u]+edge[i].w){
dis[v] = dis[u] + edge[i].w;
/*
若要判断负环,将最外层循环修改为k<=V,并判断k==V时是否还有被修改的点
有则证明有负环,因为有负环会一直更新
*/
}
}
/*
求得的dis[i]即为i到root的最短路径
*/
}
第k次更新dis[i]相当于在更新:i沿着边反向走k步途中能到达的所有点形成的这个图中,i距离源点的最短路径
上一次我们求出了k-1步下每个点的单元最短路径,因此我们用所有能直接一步到达i的节点去更新即可。
当然有些点其实在第k次被更新好了,然后又去更新了别人,这样对那个被更新的点最终单源最短路的结果没有影响,就是可能提前获取了答案。
显然更新完V-1步,所有点的单源最短路都正确了,因为V-1步已经包含整张图了。还能更新说明有负环。
SPFA 算法(队列优化的Bellman_ford)
作用:求单源最短路/判负环
时间:O(V*E) (一般情况远低于)
const int N = 1e4+5;
const int M = 5e4+5;
struct Edge
{
int to,last,w;
}edge[M];
int id,head[N];
void add(int u,int v,int w)
{
edge[id].to = v;
edge[id].w = w;
edge[id].last = head[u];
head[u] = id++;
}
void init()
{
id = 1;
memset(head,0,sizeof head);
}
int val[N];//记录该节点被更新多少次
int dis[N];//记录单源最短路
void SPFA(int V)//V表示节点数
{
memset(dis,INF,sizeof dis);
memset(val,0,sizeof val);
int root = 1;//若只用于判负环,源点可以随便取
bool flag = false;//是否有负环,初始化为无
dis[root] = 0;
/*
下面注释掉的四行是SPFA的优化,节点数多的时候一般来说是会优化...
就是先让dis[]小的去松弛,这样可以让进队列的数更少
*/
//deque<int>que;
//que.pb(root);
queue<int>que;
que.push(root);
while (!que.empty()){
int now = que.front();que.pop_front();
int v;
for (int i=head[now];i!=0;i=edge[i].last){
v = edge[i].to;
if (dis[v]>dis[now]+edge[i].w){
dis[v] = dis[now] + edge[i].w;
//que.pb(v);
//if (dis[que.front()]<dis[que.back()]){que.pf(v);que.pop_back();}
que.push(v);
val[v]++;
if (val[v]==V) {flag = true;break;}
}
}
if (flag) break;
}
}
优化原理是未被松弛成功的点一定不会对下一轮松弛产生影响,因此用队列保留被松弛过的点去更新即可。
后缀自动机
const int MAXN = 250000+5;
const int ALP = 26;
struct SAM
{
int trie[MAXN<<1][ALP];
int len[MAXN<<1];
int fa[MAXN<<1];
int sz,las;
void init(){
newnode(las=sz=0);
fa[0] = -1;
}
int newnode(int x){
memset(trie[x],0,sizeof trie[x]);
len[x] = 0;
return sz++;
}
int idx(char ch){
return ch-'a';
}
void add(char ch){
int c = idx(ch);
int p = las;
int np = newnode(sz);
las = np;
len[np] = len[p]+1;
for(;~p && !trie[p][c];p=fa[p]) trie[p][c] = np;/**这里循环停下来其实很关键的,停下来的话证明上一个后缀+C的对应的串存在,那个玩意儿就是现在的最长后缀了,不会更长了,否则之前就找到末尾+c有指向的点了*/
if (p==-1) fa[np] = 0;
else {
int q = trie[p][c];
if (len[q]==len[p]+1) fa[np] = q;
else {
int nq = newnode(sz);
fa[nq] = fa[q];
for0(i,0,ALP) trie[nq][i] = trie[q][i];
len[nq] = len[p]+1;
fa[np] = fa[q] = nq;
for (;~p && trie[p][c]==q;p=fa[p]) trie[p][c] = nq;/**之前所有连到q的现在全部连到nq,因为要连到最短后缀,只需证明+C小于等于nq即可,显然的*/
}
}
}
int find(char *s){
int u=0,maxlen=0,nowlen=0;
for (int i=0;s[i];i++){
int c = idx(s[i]);
while (~u && !trie[u][c]){
u = fa[u];
if (~u) nowlen = len[u];
}
if (u==-1) {nowlen = u = 0;continue;}
u = trie[u][c];
nowlen++;
maxlen = max(maxlen,nowlen);
}
return maxlen;
}
}sam;
后缀自动机转后缀树转后缀数组
来自:https://blog.youkuaiyun.com/lvzelong2014/article/details/79006541
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;
const int N = 1e6 + 10;
int sa[N], rank[N], s[N], n;
void readchar() {
char ch = getchar(); int x = 0;
while(ch < 'a' || ch > 'z') ch = getchar();
while(ch >= 'a' && ch <= 'z') {s[++x] = ch - 'a'; ch = getchar();}
n = x;
}
struct SAM {
int ch[N][26], pos[N], len[N], fa[N], last, sz;
bool val[N];
SAM() {last = ++sz;}
void Extend(int c, int po) {
int p = last, np = last = ++sz;
pos[np] = po;///********后缀起点位置,构造后缀树用的
val[np] = true; ///********是否为后缀起点节点,构造后缀数组的时候用的
len[np] = len[p] + 1;
for(;p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
if(!p) fa[np] = 1;
else {
int q = ch[p][c];
if(len[q] == len[p] + 1) fa[np] = q;
else {
int nq = ++sz; len[nq] = len[p] + 1;
memcpy(ch[nq], ch[q], sizeof(ch[q]));
fa[nq] = fa[q];
pos[nq] = pos[q];
fa[q] = fa[np] = nq;
for(;ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
}
}
}
}sam;
struct Suffix_Tree {
int ch[N][26], pos[N], tp;
bool val[N];
void Add(int u, int v, int c) {ch[u][c] = v;}
void Build() {
for(int i = 1;i <= sam.sz; ++i) val[i] = sam.val[i], pos[i] = sam.pos[i];
for(int i = 2;i <= sam.sz; ++i)
Add(sam.fa[i], i, s[pos[i] + sam.len[sam.fa[i]]]);
}
void Dfs(int u) {
if(val[u]) sa[rank[pos[u]] = ++tp] = pos[u];
for(int i = 0 ;i < 26; ++i)
if(ch[u][i]) Dfs(ch[u][i]);
}
}suftree;
int main() {
readchar();
for(int i = n; i >= 1; --i) sam.Extend(s[i], i);
suftree.Build();
suftree.Dfs(1);
for(int i = 1;i <= n; ++i) printf("%d ", sa[i]);
putchar('\n');
return 0;
}
Treap
插入与找第k小(提醒一下引用不要漏掉)
#include<bits/stdc++.h>
using namespace std;
struct Treap
{
Treap* ch[2];
int val;
int pri;
int cnt;
Treap(int x){ch[0] = ch[1] = NULL; pri = rand(); val = x;cnt = 1;}
void maintain(){
cnt = 1;
if (ch[0]!=NULL) cnt += ch[0]->cnt;
if (ch[1]!=NULL) cnt += ch[1]->cnt;
}
};
void rotate(Treap* &rt,int d)
{
Treap* k = rt->ch[d^1];
rt->ch[d^1] = k->ch[d];
k->ch[d] = rt;
rt->maintain();
k->maintain();
rt = k;
}
void insert(Treap* &rt,int x)
{
if (rt==NULL){rt = new Treap(x);}
else {
int d = (x < rt->val)? 0:1 ;
insert(rt->ch[d],x);
if (rt->ch[d]->pri > rt->pri) rotate(rt,d^1);
}
rt->maintain();
}
int find(Treap* rt,int k)
{
if (rt->ch[0]==NULL) ccnt = 1;
else ccnt = 1 + rt->ch[0]->cnt;
if (k==ccnt) return rt->val;
else if (k<ccnt) return find(rt->ch[0],k);
else return find(rt->ch[1],k-ccnt);
}
int main()
{
Treap* root = NULL;
insert(root,1);
insert(root,3);
insert(root,5);
printf("%d\n",find(root,2));
}
笛卡尔树
const int N = 1e5+5;
int a[N],n;//用于构建的数组
int sta[N],root;//栈,根节点
int ch[N][2],l[N],r[N];//树,比自己大的最左最右下标
void build(){
int tot = -1;
for1(i,1,n){
ch[i][0] = ch[i][1] = 0;//初始化
int nowtot = tot;//用于检验栈内是否弹出了元素,否则sta[tot+1]不知道连到什么鬼玩意
while (tot>=0 && a[sta[tot]]>a[i]) tot--;
if (tot>=0) ch[sta[tot]][1] = i;
if (nowtot>tot) ch[i][0] = sta[tot+1];
sta[++tot] = i;
}
root = sta[0];
}
void dfs(int now){//获取l,r
if (ch[now][0]) dfs(ch[now][0]);
l[now] = ch[now][0] ? l[ch[now][0]]:now;
if (ch[now][1]) dfs(ch[now][1]);
r[now] = ch[now][1] ? r[ch[now][1]]:now;
}
无旋treap
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
const int N = 1e5+5;
const int maxn = 1e5+5;
struct fhq_treap
{
int ch[maxn][2];
int pri[maxn];
int val[maxn];
int cnt[maxn];
int sz,root;
void init(){
sz = 1;
root = 0;
}
void maintain(int rt){
cnt[rt] = 1 + cnt[ch[rt][0]] + cnt[ch[rt][1]];
}
int newnode(int v){
ch[sz][0] = ch[sz][1] = 0;
pri[sz] = rand();
val[sz] = v;
cnt[sz] = 1;
return sz++;
}
int merge(int x,int y){
if (!x || !y) return x+y;
if (pri[x] > pri[y]){
ch[x][1] = merge(ch[x][1],y);
maintain(x);
return x;
}
else {
ch[y][0] = merge(x,ch[y][0]);
maintain(y);
return y;
}
}
void split(int rt,int v,int& x,int& y){
if (!rt) x = y = 0;
else {
if (val[rt] <= v){
x = rt;
split(ch[rt][1],v,ch[rt][1],y);
maintain(x);
}
else {
y = rt;
split(ch[rt][0],v,x,ch[rt][0]);
maintain(y);
}
}
}
void insert(int v){
int x,y;
split(root,v,x,y);
root = merge(merge(x,newnode(v)) , y);
}
void remove(int v){
int x,y,z;
split(root,v-1,x,y);
split(y,v,y,z);
y = merge(ch[y][0],ch[y][1]);
root = merge(merge(x,y),z);
}
int rank(int v){
int x,y;
split(root,v-1,x,y);
int ans = cnt[x]+1;
root = merge(x,y);
return ans;
}
int KTH(int k){return val[kth(root,k)];}
int kth(int rt,int k){
if (k<=cnt[ch[rt][0]]) return kth(ch[rt][0],k);
k -= cnt[ch[rt][0]] + 1;
if (k<=0) return rt;
else return kth(ch[rt][1],k);
}
int pre(int v){
int x,y;
split(root,v-1,x,y);
int ans = val[kth(x,cnt[x])];
root = merge(x,y);
return ans;
}
int suc(int v){
int x,y;
split(root,v,x,y);
int ans = val[kth(y,1)];
root = merge(x,y);
return ans;
}
}ftp;
int main()
{
ftp.init();
int m,op,x;
scanf("%d",&m);
while (m--){
scanf("%d %d",&op,&x);
if (op==1) ftp.insert(x);
if (op==2) ftp.remove(x);
if (op==3) printf("%d\n",ftp.rank(x));
if (op==4) printf("%d\n",ftp.KTH(x));
if (op==5) printf("%d\n",ftp.pre(x));
if (op==6) printf("%d\n",ftp.suc(x));
}
return 0;
}
无旋treap维修数列(无旋treap登峰造极之操作)
struct fhq_treap
{
int ch[maxn][2];
int pri[maxn];
int cnt[maxn];
int val[maxn];
int sum[maxn];
int l[maxn],r[maxn],mcs[maxn];
int rev[maxn],upd[maxn];
int sta[maxn];
int root;
int nextpos[maxn],st,ed;///储存下一个可以用的节点编号
void init(){
st = 1,ed = 1;
root = 0;
l[0] = r[0] = mcs[0] = -INF;
for (int i=1;i<=500005;i++) nextpos[i] = i;
}
int newnode(int v){
int sz = nextpos[st++]; if (st==500006) st = 1;
ch[sz][0] = ch[sz][1] = 0;
pri[sz] = rand();
cnt[sz] = 1;
val[sz] = sum[sz] = l[sz] = r[sz] = mcs[sz] = v;
rev[sz] = 0;
upd[sz] = 0;
return sz;
}
void use_for_reverse(int rt){
if (!rt) return ;
rev[rt] ^= 1;
swap(l[rt],r[rt]);
}
void use_for_update(int rt,int c){
if (!rt) return ;
upd[rt] = 1;
val[rt] = c;
sum[rt] = c*cnt[rt];
l[rt] = r[rt] = mcs[rt] = max(c,sum[rt]);
}
void update(int rt){
cnt[rt] = 1 + cnt[ch[rt][0]] + cnt[ch[rt][1]];
sum[rt] = val[rt] + sum[ch[rt][0]] + sum[ch[rt][1]];
l[rt] = max(max(l[ch[rt][0]],sum[ch[rt][0]]+val[rt]),sum[ch[rt][0]]+val[rt]+l[ch[rt][1]]);
r[rt] = max(max(r[ch[rt][1]],sum[ch[rt][1]]+val[rt]),sum[ch[rt][1]]+val[rt]+r[ch[rt][0]]);
mcs[rt] = max(max(mcs[ch[rt][0]],mcs[ch[rt][1]]),max(0,r[ch[rt][0]])+val[rt]+max(0,l[ch[rt][1]]));
}
void push_down(int rt){
if (!rt) return ;
if (rev[rt]){
use_for_reverse(ch[rt][0]);
use_for_reverse(ch[rt][1]);
rev[rt] = 0;
swap(ch[rt][0],ch[rt][1]);
}
if (upd[rt]){
use_for_update(ch[rt][0],val[rt]);
use_for_update(ch[rt][1],val[rt]);
upd[rt] = 0;
}
}
int merge(int x,int y){
if (!x || !y) return x+y;
push_down(x),push_down(y);
if (pri[x] < pri[y]){///维护大根堆
ch[y][0] = merge(x,ch[y][0]);
update(y);
return y;
}
else {
ch[x][1] = merge(ch[x][1],y);
update(x);
return x;
}
}
int ccnt;
void split(int rt,int k,int& x,int& y){
if (!rt) x = y = 0;
else {
push_down(rt);
ccnt = cnt[ch[rt][0]] + 1;
if (k>=ccnt){///***这边需要一个等号,思考方法是判断此时根+左儿子是否属于前k个
x = rt;
split(ch[rt][1],k-ccnt,ch[rt][1],y);
}
else {
y = rt;
split(ch[rt][0],k,x,ch[rt][0]);
}
update(rt);
}
}
int build(int n){
int x;
int tot = -1,st_tot;
for (int i=1;i<=n;i++){
read(x);
int now = newnode(x);
st_tot = tot;
while (tot>=0){
if (pri[sta[tot]] < pri[now]){///维护大根堆
update(sta[tot]);
tot--;
}
else {
ch[sta[tot]][1] = now;
break;
}
}
if (tot < st_tot) ch[now][0] = sta[tot+1];
sta[++tot] = now;
}
while (tot>=0) update(sta[tot--]);
return sta[0];
}
void insert(int pos,int tot){
int x,y;
split(root,pos,x,y);
x = merge(x,build(tot));
root = merge(x,y);
}
void remove(int pos,int tot){
int x,y,z;
split(root,pos-1,x,y);
split(y,tot,y,z);
del(y);
root = merge(x,z);
}
void update(int pos,int tot,int c){
int x,y,z;
split(root,pos-1,x,y);
split(y,tot,y,z);
use_for_update(y,c);
root = merge(x,merge(y,z));
}
void reverse(int pos,int tot){
int x,y,z;
split(root,pos-1,x,y);
split(y,tot,y,z);
use_for_reverse(y);
root = merge(x,merge(y,z));
}
int getsum(int pos,int tot){
if (pos<=0) return 0;
int x,y,z;
split(root,pos-1,x,y);
split(y,tot,y,z);
int ans = sum[y];
root = merge(x,merge(y,z));
return ans;
}
int getmcs(){
return mcs[root];
}
void del(int rt){
if (!rt) return ;
if (ch[rt][0]) del(ch[rt][0]);
if (ch[rt][1]) del(ch[rt][1]);
nextpos[ed++] = rt;
if (ed==500006) ed = 1;
}
/* 检查数组情况
void DFS(int rt){
if (!rt) return ;
push_down(rt);
if (ch[rt][0]) DFS(ch[rt][0]);
printf("now = %d,val = %d,lmax = %d,rmax = %d\n",rt,val[rt],l[rt],r[rt]);
if (ch[rt][1]) DFS(ch[rt][1]);
}
*/
}ftp;
动态开点线段树
求逆序
#include<bits/stdc++.h>
using namespace std;
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define ll long long
#define mid int m = l+r>>1
#define tl tree[rt].l
#define tr tree[rt].r
const int N = 5e5+5;
const int maxn = 500000*32 + 5;
const int MAXR = 1e9;
struct node
{
int l,r;
int sum;
}tree[maxn];
int sz;
void init(){sz = 2;}
void push_up(int rt)
{
tree[rt].sum = tree[tl].sum + tree[tr].sum;
}
void update(int p,int& rt,int l,int r)
{
//printf("update[%d,%d]\n",l,r);
if (!rt){
rt = sz++;
tl = tr = tree[rt].sum = 0;
}
if (l==r){
tree[rt].sum++;
return;
}
mid;
if (p<=m) update(p,tl,l,m);
else update(p,tr,m+1,r);
push_up(rt);
}
ll query(int L,int R,int rt,int l,int r)
{
//printf("query[%d,%d]\n",l,r);
if (!rt) return 0;
if (L<=l && r<=R) return tree[rt].sum;
ll ans = 0;
mid;
if (L<=m) ans += query(L,R,tl,l,m);
if (R>m) ans += query(L,R,tr,m+1,r);
return ans;
}
int main()
{
init();
int n;
scanf("%d",&n);
ll ans = 0;
int root = 1;
for1(i,1,n){
int x;
scanf("%d",&x);
if (x+1<=MAXR) ans += query(x+1,MAXR,1,1,MAXR);//防止区间无效
update(x,root,1,MAXR);
}
printf("%lld\n",ans);
return 0;
}
可持久化线段树(主席树)
静态区间第k小
int a[N];
int data[N];
void discrete(int n)//使用该函数把a[i]变成原本a[i]离散化后对应的数,data可以根据离散化后的值推实际的大小
{
for1(i,1,n) data[i] = a[i];
sort(data+1,data+1+n);
int cnt = unique(data+1,data+1+n) - data;
for1(i,1,n) a[i] = lower_bound(data+1,data+cnt,a[i]) - data;
}
struct node
{
int l,r;
int sum;
}tree[M];
int sz,root[N];
void push_up(int rt){
tree[rt].sum = tree[tl].sum + tree[tr].sum;
}
void update(int old,int p,int& rt,int l,int r)
{
rt = sz++;//这里容易脑抽写成if(!rt) rt = sz++
if (l==r){
tree[rt].sum = tree[old].sum + 1;
return ;
}
tree[rt] = tree[old];
mid;
if (p<=m) update(tl,p,lson);
else update(tr,p,rson);
push_up(rt);
}
int ccnt;
int query(int old,int k,int rt,int l,int r)
{
if (l==r) return data[l];
mid;
ccnt = tree[tl].sum - tree[tree[old].l].sum;
if (ccnt >= k) return query(tree[old].l,k,lson);
else return query(tree[old].r,k-ccnt,rson);
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
sz = 1;
for1(i,1,n) scanf("%d",a+i);
discrete(n);
for1(i,1,n) update(root[i-1],a[i],root[i],1,n);
int l,r,k;
for1(i,1,m){
scanf("%d %d %d",&l,&r,&k);
printf("%d\n",query(root[l-1],k,root[r],1,n));
}
return 0;
}
可持久化无旋treap
struct PBT
{
int ch[2];
int val;
int cnt;
int pri;
}t[N*50];
int root[N],sz,nowv;
inline int rnd(){
static int seed=703;
return seed=int(seed*48271LL%2147483647);
}
void update(int rt){
t[rt].cnt = 1 + t[t[rt].ch[0]].cnt + t[t[rt].ch[1]].cnt;
}
int newnode(int v){
t[sz].ch[0] = t[sz].ch[1] = 0;
t[sz].cnt = 1;
t[sz].pri = rnd();
t[sz].val = v;
return sz++;
}
int merge(int x,int y){
if (!x || !y) return x+y;
else {
int now = sz++;
if (t[x].pri < t[y].pri){
t[now] = t[x];
t[now].ch[1] = merge(t[now].ch[1],y);
}
else {
t[now] = t[y];
t[now].ch[0] = merge(x,t[now].ch[0]);
}
update(now);
return now;
}
}
void split(int rt,int v,int& x,int& y){
if (!rt) x = y = 0;
else {
int now = sz++;
t[now] = t[rt];
if (t[now].val<=v){
x = now;
split(t[x].ch[1],v,t[x].ch[1],y);
}
else {
y = now;
split(t[y].ch[0],v,x,t[y].ch[0]);
}
update(now);
}
}
void insert(int old,int v,int& rt){
int x,y;
split(root[old],v,x,y);
rt = merge(merge(x,newnode(v)),y);
}
void Insert(int old,int v){insert(old,v,root[nowv++]);}
void remove(int old,int v,int& rt){
int x,y,z;
split(root[old],v-1,x,y);
split(y,v,y,z);
y = merge(t[y].ch[0],t[y].ch[1]);
rt = merge(merge(x,y),z);
}
void Remove(int old,int v){remove(old,v,root[nowv++]);}
int rnk(int old,int v){
root[nowv++] = root[old];
int x,y;
split(root[old],v-1,x,y);
return t[x].cnt + 1 ;
}
int Rank(int old,int v){return rnk(old,v) -1;}
int kth(int rt,int k){
if (t[t[rt].ch[0]].cnt >= k) return kth(t[rt].ch[0],k);
k -= t[t[rt].ch[0]].cnt+1;
if (k<=0) return rt;
else return kth(t[rt].ch[1],k);
}
int Kth(int old,int k){
root[nowv++] = root[old];
return t[kth(root[old],k+1)].val;
}
int Pre(int old,int v){
root[nowv++] = root[old];
int x,y;
split(root[old],v-1,x,y);
return t[kth(x,t[x].cnt)].val;
}
int Suc(int old,int v){
root[nowv++] = root[old];
int x,y;
split(root[old],v,x,y);
return t[kth(y,1)].val;
}
void init(){
sz = nowv = 1;
root[0] = 0;
//insert(0,-2147483647,root[0]);
//insert(0,2147483647,root[0]);
}
后缀平衡树
BZOJ没了,我也没了
树链剖分
解决树上的链,子树修改与查询(配合线段树)
const int N = 1e5+5;
int top[N];//当前点所在链的头节点
int id[N];//区间更新时实际的区间位置
int dep[N];//深度
int fa[N];//父节点
int son[N];//重儿子节点
int size[N];//子树大小
int w[N];//节点权值
int a[N];//节点初值在线段树上对应位置的初值。a[id[i]] = w[i]
void dfs1(int now,int f,int nowdep){
dep[now] = nowdep;
fa[now] = f;
size[now] = 1;
for (int i=head[now];i!=0;i=edge[i].last){
int v = edge[i].to;
if (v==f) continue;
dfs1(v,now,nowdep+1);
size[now] += size[v];
if (size[v] > size[son[now]]) now = v;
}
}
int tot = 1;
void dfs2(int now,int topf){
id[now] = tot++;
a[id[now]] = w[now];//每个节点初始有权值,a用于更新初始权值
top[now] = topf;
if (!son[now]) return ;
dfs2(son[now],topf);
for (int i=head[now];i!=0;i=edge[i].last){
int v = edge[i].to;
if (v==fa[now] || v==son[now]) continue;
dfs2(v,v);
}
}
void interval(int x,int y){//区间修改:O(logn*logn)
while (top[x]!=top[y]){
if (dep[top[x]] < dep[top[y]]) swap(x,y);
//处理区间 [id[top[x]],id[x]]
x = fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
//处理区间 [id[y],id[x]]
}
/*
操作某棵子树x:由于区间的连续性//O(logn)
所以转换成处理区间[id[x],id[x]+size[x]-1]
*/
线性基
合并:我插你
交集:带着自己的成绩等人来救你吧
#define ll long long
ll lb[65];
bool insert(ll x){
for (int i=62;i>=0;i--){
if ((1LL<<i)&x){
if (lb[i+1]) x ^= lb[i+1];
else {lb[i+1] = x;return true;}
}
if (!x) return false;
}
}
Splay(伸展树)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define pt(x) printf("%s = %I64d\n",#x,(ll)x)
#define INF ((1LL<<31)-1)//不加括号-INF出问题
const int N = 1e5+5;
struct SPLAY
{
int ch[N][2];
int fa[N];
int size[N];
int cnt[N];
int val[N];
int sz,root;
void init(){
sz = 1;
root = 0;
}
int newnode(int v,int f){
ch[sz][0] = ch[sz][1] = 0;
fa[sz] = f;
size[sz] = cnt[sz] = 1;
val[sz] = v;
return sz++;
}
void push_up(int rt){
size[rt] = cnt[rt] + size[ch[rt][0]] + size[ch[rt][1]];
}
void rotate(int x){
int f = fa[x];
int d = ch[f][0]==x? 1:0;
fa[x] = fa[f],fa[f] = x,fa[ch[x][d]] = f;
ch[f][d^1] = ch[x][d];
ch[x][d] = f;
if (fa[x]) ch[fa[x]][ch[fa[x]][0]==f?0:1] = x;
push_up(f);push_up(x);
}
void splay(int x,int goal=0){
while (fa[x] != goal){
int f = fa[x],g = fa[f];
if (g!=goal) rotate((ch[f][0]==x)==(ch[g][0]==f)? f:x);
rotate(x);
}
if (!goal) root = x;
}
void insert(int v){
int now = root,f=0;//初始化为0很有必要
while (now && val[now]!=v) f = now,now = ch[now][v>val[now]];//第一遍说:这里是大于
if (now) cnt[now]++;
else {now = newnode(v,f);if (f)ch[f][v>val[f]] = now;}
splay(now);//通过splay重构了一下树,并(主要是)正确更新了原本由于新插入一个节点而产生错误的祖先节点的信息
}
void remove(int v){
rank(v);//找点并splay,因为还不知道v在哪,不知道从哪splay
if (cnt[root]>1) cnt[root]--;
else if (!ch[root][0] && !ch[root][1]) root = 0;
else if (!ch[root][0] || !ch[root][1]) root = ch[root][0]?ch[root][0]:ch[root][1],fa[root] = 0;//fa[root] = 0很关键,不然这个点旋转时会重新搞出删掉的节点
else {
int p = getpre(v,true);//保证有前驱后继,因为左右子树都有才能进这个else
int old = root;
splay(p);//这里知道点在哪儿,直接splay ,前驱直接splay到根的话,根一定在右儿子(不会三点一线,消失不见)
fa[ch[old][1]] = p;
ch[p][1] = ch[old][1];
push_up(p);
}
}
int rank(int v){
int now = root,ans = 0;
while (now){
if (val[now]>v) now = ch[now][0];
else{
ans += size[ch[now][0]];
if (val[now]==v) {splay(now);return ans+1;}//此处需要splay的原因与求前驱后继有关
ans += cnt[now];
now = ch[now][1];
}
}
}
int kth(int k){
int now = root;
while (now){
if (k<=size[ch[now][0]]) now = ch[now][0];
else {
k -= size[ch[now][0]] + cnt[now];
if (k<=0) return val[now];
else now = ch[now][1];
}
}
}
int getpre(int v,bool getid=false){//getid = true就返回前驱的下标
int ans = -INF,id = 0;
int now = root;
while (now){
if (val[now]>=v) now = ch[now][0];
else{
if (val[now]>ans) ans = val[now],id = now;
now = ch[now][1];
}
}
return getid?id:ans;
}
int getsuc(int v,bool getid=false){
int ans = INF,id = 0;
int now = root;
while (now){
if (val[now]<=v) now = ch[now][1];
else {
if (val[now]<ans) ans = val[now],id = now;
now = ch[now][0];
}
}
return getid?id:ans;
}
void DFS(){printf("----");dfs(root);puts("");}
void dfs(int now){
if (ch[now][0]) dfs(ch[now][0]);
printf("%d ",val[now]);
//printf("val[%d]=%d,ch[%d][0]=%d,ch[%d][1]=%d\n",now,val[now],now,ch[now][0],now,ch[now][1]);
if (ch[now][1]) dfs(ch[now][1]);
}
}sp;
int main()
{
//freopen("C:/Users/DELL/Desktop/input.txt", "r" , stdin);
//freopen("C:/Users/DELL/Desktop/output.txt", "w" , stdout);
sp.init();
int q,op,x;
scanf("%d",&q);
while (q--){
scanf("%d %d",&op,&x);
if (op==1) sp.insert(x);
if (op==2) sp.remove(x);
if (op==3) printf("%d\n",sp.rank(x));
if (op==4) printf("%d\n",sp.kth(x));
if (op==5) printf("%d\n",sp.getpre(x));
if (op==6) printf("%d\n",sp.getsuc(x));
//pt(sp.root);
//sp.DFS();
}
return 0;
}
随机生成一棵树 + 随机生成两个节点询问路径第k小(对拍用)
//不保证一定没有问题
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
const int maxINT = ((1LL<<31)-1);
const int N = 1e5+5;
int Rand(bool tag=false){//生成int范围随机数,tag=true表示允许负数
int s1 = rand();
int s2 = rand();
int flag = 1;
if (tag)flag = rand()%2==0? 1:-1;
return 1LL*flag*s1*s2%maxINT;
}
//**************************************************************************
//建树
struct E{
int to,last;
}edge[N<<1];
int head[N],id;
void add(int u,int v){edge[id].to = v;edge[id].last = head[u];head[u] = id++;}
//***********************************************************************************
//并查集判断两个点是否在同一个集合中
int uf[N];
int find1(int x){
int r = x;
while (uf[r]!=r) r = uf[r];
for (int i = x,j;i!=r;i=j){
j = uf[i];
uf[i] = r;
}
return r;
}
void join(int a,int b){
a = find1(a),b = find1(b);
if (a!=b) uf[a] = b;
}
//************************************************************************************
int dep[N],lca[N][20];
void dfs(int now,int f,int depth){
lca[now][0] = f;
dep[now] = depth;
for (int i=head[now];i!=0;i=edge[i].last){
int v = edge[i].to;
if (v==f) continue;
dfs(v,now,depth+1);
}
}
void getst(int n){
for (int j=1;(1<<j)<=n;j++)
for (int i=1;i<=n;i++)
lca[i][j] = lca[lca[i][j-1]][j-1];
}
int getlca(int x,int y){
if (dep[x]<dep[y]) swap(x,y);
int differ = dep[x]-dep[y];
for (int i=0;(1<<i)<=differ;i++){
if ((1<<i)&differ){
x = lca[x][i];
}
}
if (x==y) return x;
for (int i=18;i>=0;i--){
if (lca[x][i]!=lca[y][i]) x = lca[x][i],y = lca[y][i];
}
return lca[x][0];
}
//**************************************************************************************
int main()
{
srand(time(0));
freopen("C:/Users/DELL/Desktop/input.txt", "w", stdout);//输入想要保存文件的路径
/*u*/ //修改数据组数
int T = 100;//数据组数,可修改
printf("%d\n",T);
/*d*/
/*u*/ //修改生成的树最多节点个数,必须<=1e5
int maxsize = 20;
/*d*/
while(T--){
/*u*/ //修改当前这组数据的节点数以及询问次数,q默认10次
int n = Rand()%(maxsize+1),q=10;
if (!n) n++;
/*d*/
printf("%d %d\n",n,q);
for1(i,1,n) uf[i] = i,head[i] = 0;
memset(lca,0,sizeof lca);
id = 1;
for1(i,1,n){
if (i!=1) printf(" ");
printf("%d",Rand(true));
}puts("");//生成每个节点的值
int hulue = Rand()%(n+1);
if (!hulue) hulue++;
for1(i,1,n){
if (i==hulue) continue;
int v;
do {
v = Rand()%(n+1);
if (!v) v++;
}while (find1(i)==find1(v));
join(i,v);//并查集
printf("%d %d\n",i,v);//生成数据
add(i,v);add(v,i);//生成树,用于check合法的k
}//随机生成一棵树结束
dfs(1,0,1);
getst(n);
while(q--){
int u = Rand()%(n+1),v = Rand()%(n+1);
if (!u) u++;if (!v) v++;
int LCA = getlca(u,v);
int maxk = dep[u]-dep[LCA]+dep[v]-dep[LCA]+1;
int k = Rand()%(maxk+1);
if (!k) k++;
printf("%d %d %d\n",u,v,k);
}
}
return 0;
}
FFT+NTT的准备工作
struct cp{
double x,y;
cp(double xx=0,double yy=0){x=xx;y=yy;}
double real(){return x;}
double imag(){return y;}
cp friend operator + (cp a,cp b){return cp(a.x+b.x,a.y+b.y);}
cp friend operator - (cp a,cp b){return cp(a.x-b.x,a.y-b.y);}
cp friend operator * (cp a,cp b){return cp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
};
int rev[N];
void getrev(int n){//获取下标二进制下翻转后对应的位置的数组0~n-1
int bit = 1;
while ( (1<<bit) < n ) bit++;
bit--;
for0(i,0,n) rev[i] = (rev[i>>1]>>1) + ((1&i)<<bit);
}
ll ft(ll a,ll b){
ll ans = 1;
while (b){
if (b&1) ans = ans*a%mod;
b>>=1;
a = a*a%mod;
}
return ans;
}
ll getny(ll a){
return ft(a,mod-2);
}
FFT
void FFT(cp a[],int RB,int inv){
for0(i,0,RB){
if (i<rev[i]) swap(a[i],a[rev[i]]);
}
for (int n = 2;n <= RB;n <<= 1){
int mid = n>>1;
cp wn = cp(cos(2*pi*1/n),inv*sin(2*pi*1/n));//总共只需调用logn次三角函数,这里是TLE的关键
for (int st = 0;st < RB;st += n){
cp wnk = cp(1,0);
for0(i,st+0,st+mid){
cp x = wnk*a[i+mid];
a[i+mid] = a[i] - x;
a[i] = a[i] + x;
wnk = wnk*wn;//模长相乘 极角相加的运用
}
}
}
if (inv==-1){
for0(i,0,RB) a[i].x = (int)(a[i].real()/RB + 0.5);
}
}
NTT
void NTT(ll a[],int RB,int inv){
for0(i,0,RB) if (i<rev[i]) swap(a[i],a[rev[i]]);
for (int n = 2;n <= RB; n <<= 1){
int mid = n/2;
ll wn = ft(g,(mod-1)/n);// 2*pi* 1/n --> (mod-1)* 1/n
if (inv==-1) wn = getny(wn);
for (int st = 0;st < RB;st += n){
ll wnk = 1;//cp(1,0) --> 1
for0(i,st+0,st+mid){
int x = wnk*a[i+mid]%mod;
a[i+mid] = (a[i] - x + mod)%mod;//减法记得加个模
a[i] = (a[i] + x)%mod;
wnk = wnk*wn%mod;
}
}
}
if (inv==-1) {
ll rbny = getny(RB);
for0(i,0,RB) a[i] = a[i]*rbny%mod;//注意这里不是/RB了
}
}