NOIP 算法合集

news/2025/11/28 21:24:49/文章来源:https://www.cnblogs.com/cogimyun/p/-/algorithm

Ⅰ​.数据结构

1.树状数组

时间复杂度:\(O(n\log n)\)

优点:常数小

缺点:可以维护的内容不如线段树

应用:小常数维护前缀和或单点值

int tr[200005];
void add(int x,int y){while(x<=n)tr[x]+=y,x+=(-x)&x;}
int query(int x){int res=0;while(x)res+=tr[x],x-=(-x)&x;return res;
}

2.线段树

时间复杂度:\(O(n\log n)\)

优点:可以高效维护各类信息

缺点:常数较大

应用:维护各类可以跨越区间计算的信息

值得注意的是,如果可以离散化的线段树尽量不要使用动态开点线段树,常数和空间比较大,具体的,动态开点线段树见可持久化线段树与线段树合并的实现

int tr[800005],tag[800005];
void add(int p,int s,int t,int l,int r,int x){if(l<=s&&t<=r){tr[p]+=(t-s+1)*x;tag[p]+=x;return;}int mid=(s+t)>>1;if(tag[p]){tr[p<<1]+=(mid-s+1)*tag[p];tr[p<<1|1]+=(t-mid)*tag[p];tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];tag[p]=0;}if(l<=mid)add(p<<1,s,mid,l,r,x);if(r>mid)add(p<<1|1,mid+1,t,l,r,x);tr[p]=tr[p<<1]+tr[p<<1|1];
}
int query(int p,int s,int t,int l,int r){if(l<=s&&t<=r)return tr[p];int mid=(s+t)>>1,res=0;if(tag[p]){tr[p<<1]+=(mid-s+1)*tag[p];tr[p<<1|1]+=(t-mid)*tag[p];tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];tag[p]=0;}if(l<=mid)res=query(p<<1,s,mid,l,r);if(r>mid)res+=query(p<<1|1,mid+1,t,l,r);return res;
}

3. 可持久化线段树(主席树)

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n\log n)\)

应用:可以维护区间第 \(K\) 大,维护区间大于 \(or\) 小于 \(k\) 的连续线段 虽然不在考纲里,但感觉挺好用的,详见 [NOIP 2024 T4 树上查询]

struct node{int l,r,val;
}tr[4000005];
int cnt;//注意使用动态开点线段树
void add(int v,int &p,int l,int r,int x,int y){if(!p)p=++cnt;if(l==r){tr[p]=tr[v]+y;return;}int mid=(l+r)>>1;if(x<=mid){tr[p].r=tr[v].r;add(tr[v].l,tr[p].l,l,r,x,y);}else{tr[p].l=tr[v].l;add(tr[v].r,tr[p].r,l,r,x,y);}tr[p].val=tr[tr[p].l].val+tr[tr[p].r].val;
}
int query(int p,int l,int r,int x){if(l==r)return tr[p].val;int mid=(l+r)>>1;if(x<=mid)return query(p<<1,l,mid,x);return query(p<<1|1,mid+1,r,x);
}

4.线段树合并

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n\log n)\)

应用:维护一些子树上的信息,并且可以在子树合并时维护父节点子树信息,或者维护其他的多种类合并问题

struct node{int l,r,val;
}tr[4000005];
int cnt;//依旧动态开点线段树
int merge(int u,int v){if(!u||!v)return u|v;if(!tr[u].l&&!tr[u].r&&!tr[v].l&&!tr[v].r){tr[u].val+=tr[v].val;return u;}tr[u].l=merge(tr[u].l,tr[v].l);tr[u].r=merge(tr[u].r,tr[v],r);tr[u].val=tr[tr[u].l].val+tr[tr[u].r].val;return u;
}

5.扫描线

时间复杂度:\(O(n\log n)\)

应用:计算矩形面积并,二维数点

#include<bits/stdc++.h>
using namespace std;
const int MAXX=2e5;
struct node{int x,l,r,val;
}a[400005];//注意二倍空间
struct node{int cnt,val;
}tr[200005];
int n;
void add(int p,int s,int t,int l,int r,int x){if(l<=s&&t<=r){tr[p].cnt+=x;if(tr[p].cnt)tr[p].val==t-s+1;else{tr[p].val=0;if(s!=t)tr[p].val=tr[p<<1].val+tr[p<<1|1].val;}return;}int mid=(s+t)>>1;if(l<=mid)add(p<<1,s,mid,l,r,x);if(r>mid)add(p<<1|1,mid+1,t,l,r,x);if(!tr[p].cnt)tr[p].val=tr[p<<1].val+tr[p<<1|1].val;
}
int main(){cin>>n;for(int i=1;i<=n;i++){int x,y,l,r;cin>>x>>y>>l>>r;a[i]={x,l,r,1};a[i+n]={y+1,l,r,-1};}sort(a+1,a+1+2*n,[](node x,node y){return x.x<y.x});int ans=0,la=0;for(int i=1;i<=2*n;i++){ans+=(tr[i].x-la)*tr[1].val;la=tr[i].x;while(la==tr[i].x)add(1,1,MAXX,tr[i].l,tr[i].r,tr[i++].val);i--;}cout<<ans;return 0;
}

6.平衡树

时间复杂度:\(O(n\log n)\)

应用:维护排名信息,斜率优化时维护凸包

  1. \(treap\) 感觉各方面不如 \(fhq\ treap\)
  2. \(fhq\ treap\) 好写 ,还支持可持久化
  3. \(splay\) 可以用于维护 \(LCT\) 但目前用处不大

给出 \(fhq\ treap\) 代码:大抵是只记得怎么敲 fhq 了

mt19937 rng(time(NULL));
struct node{int l,r,pri,val,sz;
}tr[200005];
int cnt;
void split(int p,int &l,int &r,int x){if(!p){l=r=0;return;}if(tr[p].val<=x){l=p;split(tr[p].r,tr[p].r,r,x);}else{r=p;split(tr[p].l,l,tr[p].l,x);}tr[p].sz=tr[tr[p].l].sz+tr[tr[p].r].sz+1;
}
int merge(int u,int v){if(!u||!v)return u|v;if(tr[u].pri>tr[v].pri){tr[u].r=merge(tr[u].r,v);tr[u].sz=tr[tr[u].l].sz+tr[tr[u].r].sz+1;return u;}tr[v].l=merge(l,tr[v].l);tr[v].sz=tr[tr[v].l].sz+tr[tr[v].r].sz+1;
}
void insert(int &rt,int x){int l,r,p;split(rt,l,r,x);p=++cnt;tr[p].pri=rng();tr[p].val=x;tr[p].sz=1;rt=merge(merge(l,p),r);
}
void del(int &rt,int x){int l,r,p;split(rt,l,r,x);split(l,l,p,x-1);rt=merge(l,r);
}

7.字典树

时间复杂度:\(O(n\log|S|)\)

空间复杂度:\(O(\sum|S|)\)

应用:可以快速查询字符串前缀信息

struct node{int son[26],cnt; 
}tr[200005];
int cnt;
void insert(string s){int p=0;for(auto i:s){if(tr[p].son[i-'a'])tr[p].son[i-'a']=++cnt;p=tr[p].son[i-'a'];}tr[p].cnt++;
}
int query(string s){int p=0,res=0;for(auto i:s){if(!tr[p].son[i-'a'])break;p=tr[p].son[i-'a'];res+=tr[p].cnt;}return res;
}

8.\(01-Trie\)

时间复杂度:\(O(n\log|S|)\)

空间复杂度:\(O(\sum|S|)\)

应用:快速查询数字在二进制下的信息,尤其是异或类型的

struct node{int son[2]; 
}tr[200005];
int cnt;
void insert(int x){int p=0;for(int i=29;i>=0;i--){int k=(x>>i)&1;if(tr[p].son[k])tr[p].son[k]=++cnt;p=tr[p].son[k];}
}
int query(int x){int p=0,res=0;for(int i=29;i>=0;i--){int k=(x>>i)&1;if(tr[p].son[k^1]){res+=(1<<i);p=tr[p].son[k^1];}else p=tr[p].son[k];}return res;
}

9.\(01-Trie\) 合并与可持久化 \(01-Trie\)

时间复杂度:\(O(n\log|S|)\)

空间复杂度:\(O(\sum|S|\log \sum|S|)\)

我们不难发现,\(01-Trie\) 和线段树都是二叉树,所以
无论是合并还是可持久化,\(01-Trie\) 和线段树在本质上都是完全相同的,实现方法与过程几乎一模一样,所以实现可以参考线段树合并与可持久化线段树,这里不给出具体的实现方法。

10.\(k\) 叉哈夫曼树

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n)\)

应用:哈夫曼编码,计算叶权值 \(\times\) 到根路径长的最小值

注意点:\(k\geq 3\) 时不同于二叉哈夫曼树,此时需要补充 \(((k-1)-[(n-1)mod(k-1)]\hspace{0.5em} m\in[0,k)\ )\)个空节点

#include<bits/stdc++.h>
using namespace std;
priority_queue<int> q;
int n,k;
int main(){cin>>n>>k;if(n%(k-1)!=1){int m=k-1-((n-1)%(k-1));for(int i=0;i<m;i++)q.push(0);}for(int i=0;i<n;i++){int x;cin>>x;q.push(x);}while(q.size()!=1){int res=0;for(int i=0;i<k;i++)res+=q.top(),q.pop();q.push(res);}cout<<q.top();return 0;
}

11.树链剖分

时间复杂度:\(O(n)\),使用其他 \(O(n\log n)\) 的数据结构时会使其时间复杂度上升为 \(O(n\log^2 n)\)

空间复杂度:\(O(n)\)

应用:由于一个树上节点到根最多经过 \(\log n\) 个重链,所以可以帮助我们将树剖分成链,然后使用其他数据结构维护树上信息

vector<int> e[200005];
int f[200005],top[200005],idx,id[200005],max_son[200005],sz[200005];
void dfs(int x,int fa){sz[x]=1;int maxx=0;for(auto i:e[x])if(i!=fa){f[i]=x;dfs(i,x);sz[x]+=sz[i];if(sz[i]>maxx)maxx=sz[i],max_son[x]=i;}
}
void dfs1(int x,int fa,int id){::id[x]=id;top[x]=fa;if(!max_son[x])return;dfs1(max_son[x],fa,id);for(auto i:e[x])if(i!=f[x]&&i!=max_son[x])dfs1(i,i,++idx);
}

12.树套树

时间复杂度:\(O(n\log^2 n)\)

空间复杂度:\(O(n^2)\),但动态开点可以使其空间复杂度达到 \(O(n\log^2 n)\)

应用:维护一些树上每个点带 \(O(n)\) 级别信息或者二维信息,通常有线段树套线段树,线段树套平衡树,\(Trie\) 树套线段树……

class{struct node{int val,l,r;}tr[14000000];
public:	int cnt=0;void modify(int &p,int l,int r,int x,int y){if(!p)p=++cnt;if(l==r){tr[p]+=y;return;}int mid=(l+r)>>1;if(x<=mid)modify(tr[p].l,l,mid,x,y);else modify(tr[p].r,mid+1,r,x,y);if(tr[p].l)tr[p].val+=tr[tr[p].l].val;if(tr[p].r)tr[p].val+=tr[tr[p].r].val;}int query(int p,int s,int t,int l,int r){if(l<=s&&t<=r)return tr[p].val;int res=0,mid=(s+t)>>1;if(l<=mid&&tr[p].l)res+=query(tr[p].l,s,mid,l,r);if(r>mid&&tr[p].r)res+=query(tr[p].r,mid+1,t,l,r);}
}T;
int rt[200005];
void build(int p,int l,int r){rt[p]=++T.cnt;int mid=(l+r)>>1;build(p<<1,l,mid);build(p<<1|1,mid+1,r);return;
}
void add(int p,int l,int r,int x,int y,int z){T.modify(rt[p],1,n,y,z);if(l==r)return;int mid=(l+r)>>1;if(x<=mid)add(p<<1,l,mid,x,y,z);else add(p<<1|1,mid+1,r,x,y,z);
}
int query(int p,int s,int t,int l,int r,int x,int y){if(l<=s&&t<=r)return T.query(rt[p],1,n,x,y);int res=0,mid=(s+t)>>1;if(l<=mid)res=query(p<<1,s,mid,l,r,x,y);if(r>mid)res+=query(p<<1|1,mid+1,t,l,r,x,y);return res;
}

13.\(cdq\) 分治

时间复杂度:处理 \(k\) 维偏序的时间复杂度是 \(O(n\log^{k-1} n)\)

空间复杂度:\(O(n)\)

应用:通常用来处理 \(k\) 维偏序数点的问题,还可以维护凸包

值得注意的是,如果存在一维不等号可以取等,应该将其放在最内层用树状数组解决,如果有多维不等号可以取等应当注意去重

#include<bits/stdc++.h>
using namespace std;
struct node{int x,y,z,idx,id;
}a[200005];
int n,ans[200005];
int tr[200005];
void add(int x,int y){while(x<=n)tr[x]+=y,x+=(-x)&x;}
int query(int x){int res=0;while(x)res+=tr[x],x-=(-x)&x;return res;
}
void cdq(int l,int r){int mid=(l+r)>>1;cdq(l,mid);cdq(mid+1,r);vector<node>tmp;int ll=l,rr=mid+1;while(ll<=mid&&rr<=r){if(a[ll].y<a[rr].y)tmp.push_back(a[ll++]);else tmp.push_back(a[rr++]);}while(ll<=mid)tmp.push_back(a[ll++]);while(rr<=mid)tmp.push_back(a[rr++]);for(int i=0;i<r-l+1;i++)a[i+l]=tmp[i];for(int i=l;i<=r;i++){if(a[i].id<=mid)add(a[i].z,1);else ans[a[i].idx]+=query(a[i].z-1);}for(int i=l;i<=r;i++)if(a[i].id<=mid)add(a[i].z,-1);
}
int main(){cin>>n;for(int i=1;i<=n;i++)cin>>a[i].x>>a[i].y>>a[i].z,a[i].idx=i;sort(a+1,a+1+n,[](node x,node y){return x.x<y.x;});for(int i=1;i<=n;i++)a[i].id=i;cdq(1,n);for(int i=1;i<=n;i++)cout<<ans[i]<<endl;return 0;
}

14.并查集

时间复杂度:\(O(n\log n)\ or\ O(n\alpha(n))\)

空间复杂度:\(O(n)\)

应用:用于维护连通性,维护连通块大小

值得注意的是,没有任何优化的并查集时间复杂度是 \(O(n\log n)\) 的,然而路径压缩并查集的时间复杂度经姚期智证明平均时间复杂度是 \(O(n\alpha(n))\) 的,经 Tarjan 证明最劣时间复杂度的下限是 \(O(n\log n)\) 的,所以建议使用路径压缩和按秩合并的并查集,其时间复杂度是严格 \(O(n\alpha(n))\)

int f[200005],sz[200005];
int getf(int x){return (f[x]==x)?x:f[x]=getf(f[x]);}
void merge(int u,int v){int fu=getf(u),fv=getf(v);if(fu!=fv){if(sz[fu]>sz[fv]){f[fv]=fu;sz[fu]+=sz[fv];}else{f[fu]=fv;sz[fv]+=sz[fu];}}
}

15.拓展域并查集

时间复杂度:\(O(n\log n)\ or\ O(n\alpha(n))\)

空间复杂度:\(O(n)\)

应用:维护存在多类对立集合的连通性与矛盾
值得注意的是的是,我们维护拓展域并查集是应保持其对称性,即 \(a\)\(b+n\) 在同一集合内,则 \(a+n\)\(b\) 也应当在同一集合内,同理 \(a\)\(b\) 在同一集合内,则 \(a+n\)\(b+n\) 也应当在同一集合内

由于除了维护对象不同外,与并查集几乎没有任何区别,故不放代码先咕着

16.边带权并查集

时间复杂度:\(O(n\log n)\ or\ O(n\alpha(n))\)

空间复杂度:\(O(n)\)

应用:维护集合间各元素有值上关系的情况

由于除了权值维护外,与并查集几乎没有任何区别,故不放代码继续咕着

17.优先队列/堆

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n)\)

应用:建议使用优先队列,可以小常数维护最大最小值,其一般与反悔贪心等其他算法一起使用
值得注意的是,如果你尝试重载优先队列的比较运算符,注意 return true 表示的是前者比后者的有限级更高

priority_queue<int> q;//默认大根堆
priority_queue<int,vector<int>,greater<int>> p;//小根堆
struct node{int u,v,w;
};
auto cmp=[](node x,node y){return x.w<y.w;};
priority_queue<node,vector<node>,function<bool(node,node)>> s(cmp);//自定义优先级

18.单调队列

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:维护移动窗口即前缀定长为 \(k\) 的区间最值,维护凸包

#include<bits/stdc++.h>
using namespace std;
int n,k,a[200005],q[200005],en,st;
int main(){cin>>n>>k;for(int i=1;i<=n;i++){while(st!=en&&q[st]<=i-k)st++;while(st!=en&&a[q[en-1]]<=a[i])en--;q[en++]=a[i];cout<<q[st]<<" ";}return 0;
}

19.单调栈

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:找左侧第一个小于当前元素的元素,维护前缀单调性,维护凸包,可以更简便地代替部分平衡树功能

#include<bits/stdc++.h>
using namespace std;
int n,k,a[200005],q[200005],top;
int main(){cin>>n>>k;for(int i=1;i<=n;i++){while(top&&a[q[top-1]>=a[i])top--;if(!top)cout<<-1<<" ";else cout<<q[top-1]<<" ";q[top++]=i;}return 0;
}

20.分块

时间复杂度:\(O(n\sqrt{n})\)

空间复杂度:\(O(n\sqrt{n})\)

应用:感觉非常广耶,也可以暴力维护多类信息,比如区间众数,区间最小……

#include<bits/stdc++h.>
using namespace std;
int n,q,a[200005],s[460][200005],f[460][460],bl,k,cnt[200005];
int main(){cin>>n>>q;for(int i=0;i<n;i++)cin>>a[i];bl=sqrt(n);k=n/bl;if(k*bl<n)k++;for(int i=1;i<=k;i++){for(int j=(i-1)*bl;j<i*bl&&j<n;j++)s[i][a[j]]++;for(int j=1;j<=n;j++)s[i][j]+=s[i-1][j];}for(int i=1,id=0,maxx=0;i<=k;i++,id=0,maxx=0,memset(cnt,0,sizeof cnt))for(int j=i;j<=k;j++){for(int l=(j-1)*bl;l<j*bl&&j<n;j++){cnt[a[l]]++;if(cnt[a[l]]>maxx||(cnt[a[l]]==maxx&&a[l]<id))maxx=cnt[a[l]],id=a[l];}f[i][j]=a[l];}while(q--){int l,r,ll,rr;cin>>l>>r;l--,r--;ll/=k,rr/=k;ll++,rr++;if(ll>=rr-1){int maxx=0,id=0;for(int i=l;i<=r;i++){cnt[a[i]]++;if(cnt[a[i]]>maxx||(cnt[a[i]]==maxx&&a[i]<id))maxx=cnt[a[i]],id=a[i];}for(int i=l;i<=r;i++)cnt[a[i]]--;cout<<id<<endl;}else{int maxx=0,id=0;for(int i=l;i<ll*bl;i++){cnt[a[i]]++;if(cnt[a[i]]+s[rr-1][a[i]]-s[ll][a[i]]>maxx||(cnt[a[i]]+s[rr-1][a[i]]-s[ll][a[i]]==maxx&&a[i]<id))maxx=cnt[a[i]]+s[rr-1][a[i]]-s[ll][a[i]],id=a[i];}for(int i=(rr-1)*bl;i<=r;i++){cnt[a[i]]++;if(cnt[a[i]]+s[rr-1][a[i]]-s[ll][a[i]]>maxx||(cnt[a[i]]+s[rr-1][a[i]]-s[ll][a[i]]==maxx&&a[i]<id))maxx=cnt[a[i]]+s[rr-1][a[i]]-s[ll][a[i]],id=a[i];}if(s[rr-1][f[ll+1][rr-1]]-s[ll][f[ll+1][rr-1]]>maxx||(s[rr-1][f[ll+1][rr-1]]-s[ll][f[ll+1][rr-1]]==maxx&&f[ll+1][rr-1]<id))maxx=s[rr-1][f[ll+1][rr-1]]-s[ll][f[ll+1][rr-1],id=f[ll+1][rr-1];for(int i=l;i<ll*bl;i++)cnt[a[i]]--;for(int i=(rr-1)*bl;i<=r;i++)cnt[a[i]]--;cout<<id<<endl;}}return 0;
}

21.莫队

时间复杂度:\(O(n\sqrt{n})\)

时间复杂度:\(O(n)\)

应用:暴力查询区间可转移信息,例如区间数颜
注意使用奇偶优化莫队

#include<bits/stdc++.h>
using namespace std;
int col[200005],cnt[200005],sum,n,q,ans[200005];
struct node{int l,r,id;
}b[200005];
int main(){cin>>n>>q;for(int i=1;i<=n;i++)cin>>col[i];for(int i=1;i<=q;i++)cin>>b[i].l>>b[i].r,b[i].id=i;int bl=sqrt(n);sort(b+1,b+1+q,[](node x,node y){return(int(x.l/bl)==int(y.l/bl))?((int(x.l/bl)%2)?x.r<y.r:x.r>y.r):x/l<y.l;});int l=b[1].l,r=b[1].r;for(int i=l;i<=r;i++){cnt[col[i]]++;if(cnt[col[i]]==1)sum++;}ans[b[1].id]=sum;for(int i=2;i<=q;i++){while(b[i].l<l){l--;cnt[col[l]]++;if(cnt[col[l]]==1)sum++;}while(b[i].r>r){r++;cnt[col[r]]++;if(cnt[col[r]]==1)sum++;}while(b[i].l>l){cnt[col[l]]--;if(!cnt[col[l]])sum--;l++;}while(b[i].r<r){cnt[col[r]]--;if(!cnt[col[r]])sum--;r--;}ans[b[i].id]=sum;}for(int i=1;i<=q;i++)cout<<ans[i]<<endl;return 0;
}

22.带修莫队

时间复杂度:\(O(n^{\frac{5}{3}})\)

空间复杂度:\(O(n)\)

应用:用于实现一些不存在修改时可以用莫队实现但又有修改的题目

值得注意的是,与莫队不同,莫队的块长是 \(\sqrt{n}\),带修莫队的块长是 \(n^{\frac{2}{3}}\)
由于除了带一个时间戳用于修改外与普通莫队没有任何区别,所以就不放代码了

\(Q.E.D\)

Ⅱ.动态规划

1.区间 \(dp\)

时间复杂度:\(O(n^3)\)

空间复杂度:\(O(n^2)\)

应用:可以解决一些能够将问题拆成多个独立不相互影响的子区间问题的题目

通常转移方程式为:

\[\large dp_{i,j}=\sum\limits_{i=l}^{r-1} dp_{i,k} \cdot dp_{k+1,j} \]

下以 [NOIP 2006 提高组] 能量项链 为例

#include<bits/stdc++.h>
using namespace std;
int n,a[105],dp[105][105];
int main(){cin>>n;for(int i=0;i<n;i++)cin>>a[i];for(int i=2;i<=n;i++){for(int l=0;l+i-1<n;l++){int r=l+i-1;for(int k=l;k<r;k++)dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]+a[l]*a[k+1]*a[(r+1)%n]);}}cout<<dp[0][n-1];return 0;
}

2.环形 \(dp\)

时间复杂度:\(O(n^3)\)

空间复杂度:\(O(n^2)\)

应用:顾名思义,适用于维护有关环上信息的题目

通常直接将长度翻倍,这样就可以直接进行区间 \(dp\)
最后答案可以表示为:

\[\large \max(dp_{1,n},dp_{2,n+1},...,dp_{n,2n-1}) \]

由于与区间 \(dp\) 写法一致,所以就不放代码了

3.树形 \(dp\)

时间复杂度:\(O(n)\ or\ O(n\log n)\) 前者为直接转移贡献的时间复杂度,后者为 \(01\) 背包转移贡献的时间复杂度

空间复杂度:\(O(n)\ or\ O(nV)\)

应用:树上最大独立集,树上选点最大贡献

下以 没有上司的舞会 为例

#include<bits/stdc++.h>
using namespace std;
vector<int> e[6005];
int dp[6005][2],n,h[6005];
bitset<6005> book;
void dfs(int x){for(auto i:e[x]){dfs(i);dp[x][0]+=max(dp[i][0],dp[i][1]);dp[x][1]+=dp[i][0];}dp[x][1]+=h[x];
}
int main(){cin>>n;for(int i=1;i<=n;i++)cin>>h[i];for(int i=1;i<n;i++){int x,y;cin>>x>>y;book.set(x);e[y].push_back(x);}int rt;for(int i=1;i<=n;i++)if(!book[i]){rt=i;break;}dfs(rt);cout<<max(dp[rt][1],dp[rt][0]);return 0;
}

4.换根 \(dp\)

时间复杂度:\(O(n)\ or\ O(n\log n)\) 前者为直接转移贡献的时间复杂度,后者为 \(01\) 背包转移贡献的时间复杂度

空间复杂度:\(O(n)\ or\ O(nV)\)

应用:用于求单个根好求并且可以由一个根转移至其他根的信息,比如部分树上随机游走,多根求解题

由于洛谷没有模板题,所以放一道随机游走题,简略题意如下:

一个 \(n\) 个节点的无根树,若从某个节点 \(i\) 出发,与节点 \(i\) 连接且未到达过的节点个数为 \(t\) 则前往这些节点中某个节点的概率都是 \(\frac{1}{t}\),求从每个节点出发进行随机游走直到无路可走的期望步数是多少,注意,每次从一个结点出发进行一次完整随机游走的过程是与从上一个结点出发进行一次完整随机游走的过程相互独立的,答案对 \(998244353\) 取模

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,cnt[200005],ff[200005],f[200005],g[200005];
vector<int> e[200005];
const int mod=998244353;
int power(int x,int y){int res=1;while(y){if(y&1)res=res*x%mod;x=x*x%mod;y>>=1;}return res;
}
void dfs(int x,int fa){int res=0;for(auto i:e[x])if(i!=fa){dfs(i,x);cnt[x]++;res+=ff[i];}f[x]=(res+cnt[x])%mod*power(cnt[x]+1,mod-2)%mod;ff[x]=(res+cnt[x])%mod*power(cnt[x],mod-2)%mod;
}
void dfs1(int x,int fa,int pre=0){if(x!=1)g[x]=(f[x]+(pre+1)*power(cnt[x]+1,mod-2)%mod)%mod;else g[x]=ff[x];int res=pre;for(auto i:e[x])if(i!=fa)res+=ff[i];for(auto i:e[x])if(i!=fa){res-=ff[i];if(x==1)dfs1(i,x,(res+cnt[x]-1)*power(cnt[x]-1,mod-2)%mod);else dfs1(i,x,(res+cnt[x])*power(cnt[x],mod-2)%mod);res+=ff[i];}
}
signed main(){cin>>n;for(int i=1;i<n;i++){int x,y;cin>>x>>y;e[x].push_back(y);e[y].push_back(x);}dfs(1,1);dfs1(1,1);for(int i=1;i<=n;i++)cout<<g[i]<<endl;return 0;
}

5.状压 \(dp\)

时间复杂度:\(O(n2^n)\)

空间复杂度:\(O(n2^n)\)

应用:用于一种 \(n\) 极小,状态与全局选择相关的问题

下以 售货员的难题 为例

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,dp[(1<<20)+10][25],dis[25][25];
signed main(){memset(dp,0x7f7f7f7f,sizeof dp);cin>>n;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>dis[i][j];dp[1][1]=0;for(int i=1;i<(1<<n);i++){for(int j=1;j<=n;j++)if(!((1<<(j-1))&i)){for(int k=1;k<=n;k++)if((1<<(k-1))&i)dp[i|(1<<(j-1))][j]=min(dp[i|(1<<(j-1))][j],dp[i][k]+dis[k][j]);}}int minn=INT_MAX;for(int i=1;i<=n;i++)minn=min(minn,dp[(1<<n)-1][i]+dis[i][1]);cout<<minn;return 0;
}

6.\(FWT\) 结合状压 \(dp\)

时间复杂度:\(O(n^22^n)\)

空间复杂度:\(O(n2^n)\)

应用:可以用来代替一些不会写的 \(dp\) 比如轮廓线 \(dp\)或二维状压 \(dp\)

考虑对于状压 \(dp_S\) 通常表示的是二进制状态 \(S\) 下的方案数,对于每层的 \(dp\) 每个可以视为一个数列 \(A=\{dp_0,dp_1,..,dp_{2^n-1}\}\),于是对于考虑前 \(k\) 层后每个状态下的方案数数列记为 B,如果状态 \(i,j\) 满足关系 \(f(i,j)\) 即可转移,转移式为 \(c=a\cdot b\),其中 \(\cdot\) 为一个运算,则上面的一堆东西就可以写成一个二进制卷积形式:

\[\large B_{k,t}=\sum\limits_{f(i,j)=true} B_{k-1,i}\cdot A_{k,j} \]

不难发现 \(f(i,j)\) 是一个二进制运算时,我们就可以使用 \(FWT\)\(O(2^n\log2^n)\) 的时间复杂度下进行卷积

可以详见数学章的 \(FWT\)

7.矩阵快速幂优化 \(dp\)

时间复杂度:\(O(k^3\log n)\),其中 \(k\) 是矩阵大小

空间复杂度:\(O(k^2)\)

应用:用于转移类似于矩阵乘法或广义矩阵乘法的 \(dp\)

比如存在 \(dp\) 转移式:

\[\large dp_i=dp_{i-1}+2dp_{i-2} \]

可以写成矩阵形式:

\[\large\begin{bmatrix}dp_{i-2} & dp_{i-1} & dp_i\end{bmatrix}=\begin{bmatrix}dp_{i-3} & dp_{i-2} & dp_{i-1}\end{bmatrix}\begin{bmatrix}0 & 0 & 0\\1 & 0 & 2\\0 & 1 & 1\end{bmatrix} \]

假设初始矩阵为 \(\large\begin{bmatrix}dp_{-2} & dp_{-1} & dp_0 \end{bmatrix}=\begin{bmatrix}0 & 0 & 1\end{bmatrix}\),于是 \(n\) 次转移得到 \(dp_n\) 可以写成:

\[\large\begin{bmatrix}dp_{n-2} & dp_{n-1} & dp_n\end{bmatrix}=\begin{bmatrix}0 & 0 & 1\end{bmatrix}\begin{bmatrix}0 & 0 & 0\\1 & 0 & 2\\0 & 1 & 1\end{bmatrix}^n \]

接下来就成了矩阵快速幂的问题了,可以详见数学章的矩阵快速幂

8.动态 \(dp\)

时间复杂度:\(O(k^3\log^2 n)\),其中 \(k\) 是矩阵大小

空间复杂度:\(O(k^2n)\)

应用:用于维护树上带修改的 \(dp\)

同矩阵快速幂优化 \(dp\),动态 \(dp\) 也需将转移式改为矩阵乘法或者广义矩阵乘法,然后将树进行树链剖分,
用线段树维护重链矩阵积

9.前缀和优化 \(dp\)

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:用于维护对于前缀和转移形式的 \(dp\)

如果存在 \(dp\) 转移式为:

\[\large dp_i=\sum\limits_{j=i-k}^{i-1}dp_j \]

于是我们可以计算前缀和数组 \(S\)

\[\large S_i=\sum\limits_{i=1}^i dp_i \]

然后上面的 \(dp\) 转移式就变为了:

\[\large dp_i=s_{i-1}-s_{i-k-1} \]

10.单调队列优化 \(dp\)

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:用于维护对于前缀最值转移形式的 \(dp\)

如果存在 \(dp\) 转移式为:

\[\large dp_i=\min(dp_{i-k},dp_{i-k+1},...,dp_{i-1})+\delta \]

我们不难发现,如果 \(k\) 为常数,那么 \(\min(dp_{i-k},dp_{i-k+1},...,dp_{i-1})\) 可以使用单调队列在 \(O(n)\) 的时间复杂度下维护

详见数据结构章的单调队列

11.线段树优化 \(dp\)

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n)\)

应用:用于维护对于各类最值转移形式的 \(dp\)

如果存在 \(dp\) 转移式为:

\[\large dp_i=\min(dp_{i-k}+k*d,dp_{i-k+1}+(k-1)*d,...,dp_{i-1}+d)+\delta \]

我们就可以维护线段树维护 \(\min(dp_{i-k}+k*d,dp_{i-k+1}+(k-1)*d,...,dp_{i-1}+d)\),即区间加 \(d\) 单点赋值 \(dp_i\)

详见数据结构章的线段树

12.斜率优化 \(dp\)

时间复杂度:\(O(n)\ or\ O(n\log n)\),前者为单调队列维护凸包,后者为 \(cdq\) 分治或平衡树或李超线段树维护凸包

空间复杂度:\(O(n)\)

应用:用于维护形似一次函数转移形式的 \(dp\)

如果存在 \(dp\) 转移式为:

\[\large dp_i=\min\limits_{j<i}(dp_j+calc(i,j)) \]

如果其中 \(calc(i,j)\) 呈现一次函数形状即可实现斜率优化

值得注意的是,斜率单调,横坐标单调,可以使用单调队列实现 \(O(n)\),如果其中一个不单调,则可以使用李超线段树或 \(cdq\) 分治或平衡树或单调队列/单调栈加二分维护,实现 \(O(n\log n)\),如果都不单调,只能用李超线段树或 \(cdq\) 分治或平衡树维护,实现 \(O(n\log n)\)

数据结构内容详见数据结构章

\(Q.E.D\)

Ⅲ.字符串

1.字符串哈希

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:几乎可以实现所有字符串题目,字符串匹配等

下以 [NOIP2020] 字符串匹配 为例:

#include<bits/stdc++.h>
using namespace std;
const int base=20100611;
unsigned long long h[1100005],p[1100005];
int t,tr[30],cnt[30],sum,nxt[1100005];
long long ans;
string s;
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>t;p[0]=1;for(int i=1;i<=(1<<20);i++)p[i]=p[i-1]*base;while(t--){cin>>s;memset(tr,0,sizeof tr);memset(cnt,0,sizeof cnt);ans=sum=ans=0;int n=s.size();s=' '+s;for(int i=1;i<=n;i++)h[i]=h[i-1]*base+s[i]-'a';for(int i=n;i>=1;i--){cnt[s[i]-'a']++;if(cnt[s[i]-'a']%2)sum++;else sum--;nxt[i]=sum;}memset(cnt,0,sizeof cnt);cnt[s[1]-'a']++;sum=1;for(int i=1;i<=26;i++)tr[i]++;for(int i=2;i<n;i++){ans+=tr[nxt[i+1]];int id=1;while(i*(id+1)<n&&h[(id+1)*i]-h[id*i]*p[i]==h[i])id++,ans+=tr[nxt[id*i+1]];cnt[s[i]-'a']++;if(cnt[s[i]-'a']%2)sum++;else sum--;for(int j=sum;j<=26;j++)tr[j]++;}cout<<ans<<endl;}return 0;
}

2.\(KMP\)

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:用于单模式串匹配

#include<bits/stdc++.h>
using namespace std;
string a,b;
int kmp[200005];
int main(){cin>>a>>b;a=' '+a,b=' '+b;int id=0;for(int i=2;i<=b.size()-1;i++){while(id&&b[i]!=b[id+1])id=kmp[id];if(b[i]==b[id+1])id++;kmp[i]=id;}id=0;for(int i=1;i<=a.size()-1;i++){while(id&&a[i]!=b[id+1])id=kmp[id];if(a[i]==b[id+1])id++;if(id==b.size()-1){cout<<i-id+1<<endl;id=kmp[id];}}return 0;
}

3.\(manacher\)

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:用于寻找以每个位置为中心的最长回文串

#include<bits/stdc++.h>
using namespace std;
string a,b;
int p[400005];
int main(){cin>>a;b="*|";for(int i:a)b+=i,b+='|';b+='*';int l=0,r=0;for(int i=1;i<b.size()-1;i++){if(i>r){char *x=&b[i-1],*y=&b[i+1];while(*x==*y&&*x!='*'&&*y!='*')x--,y++;p[i]=y-&b[i];}else{int j=l+r-i;if(j-p[j]+1>l)p[i]=p[j];char *y=&b[r+1],*x=&b[2*i-r-1];while(*x==*y&&*x!='*'&&*y!='*')x--,y++;p[i]=y-&b[i];	}if(p[i]+i-1>r)r=p[i]+i-1,l=i-p[i]+1;}for(int i=1;i<b.size()-1;i+=2)cout<<p[i]<<" ";return 0;
}

4.\(AC\) 自动机

时间复杂度:\(O(\sum|S|+\sum|T|)\)

空间复杂度:\(O(\sum|S|)\)

应用:多模式串匹配

struct node{int son[27],fail,cnt,vis;
}tr[5000005];
int cnt,sum[5000005];
vector<int> e[5000005],to;
void insert(string s){int p=0;for(auto i:s){if(!tr[p].son[i-'a'])tr[p].son[i-'a']=++cnt;p=tr[p].son[i-'a'];}tr[p].cnt++;
}
void dfs(int x){for(int i:e[x])sum[i]=sum[x]+tr[i].cnt,dfs(i);}
void build(){queue<int> q;for(int i=0;i<27;i++){if(tr[0].son[i])q.push(tr[0].son[i]);}while(!q.empty()){int p=q.front();for(int i=0;i<27;i++){if(tr[p].son[i])tr[tr[p].son[i]].fail=tr[tr[p].fail].son[i],q.push(tr[p].son[i]);else tr[p].son[i]=tr[tr[p].fail].son[i];}q.pop();}for(int i=1;i<=cnt;i++)e[tr[i].fail].push_back(i);dfs(0);
}
int query(string x){int p=0;int res=0;for(auto i:x){p=tr[p].son[i-'a'];res+=sum[p];}return res;
}
\(Q.E.D\)

Ⅳ.图论

1.最短路

\(Floyd\) \(SPFA\) 堆优化 \(Dijkstra\)
特点 多源 判负环
时间复杂度 \(O(n^3)\) \(O(nm)\) \(O(m\log n)\)
空间复杂度 \(O(n^2)\) \(O(n+m)\) \(O(n+m)\)

值得注意的是,关于 \(SPFA\)它死了,但可以判负环和跑差分约束,接下来讲讲差分约束
考虑式子:

\[\large a-b\le c \]

于是联想到最短路松弛式子:

\[\large dis_a\le dia_b+c \]

于是将上面式子转化为:

\[\large a\le b+c \]

那么我们可以将 \(a\) 连一条向 \(b\) 的边,长度为 \(c\),然后建一个超级源点到所有点的长度为 0 ,接下来跑一个最长路,只要不存在正环即有解,\(dis\) 即为一组特殊解

\(Floyd\)\(SPFA\) 就不加以阐述了,给出堆优化 \(dijkstra\) 的代码:

int dis[200005];
bitset<200005> book;
vector<pair<int,int>> e[200005];
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;
void dij(){memset(dis,0x3f3f3f3f,sizeof dis);dis[1]=0;q.push({0,1});while(!q.empty()){auto u=q.top();if(book[u.second])continue;book[u.second]=1;for(auto v:e[u.second])if(dis[v.first]>u.first+v.second){dis[v.first]=u.first+v.second;q.push(v.first);}}
}

2.最小生成树

时间复杂度:\(O(m\log m)\)

空间复杂度:\(O(m+n)\)

上面的复杂度分析基于 \(Kruskal\) 不会 Prim

#include<bits/stdc++.h>
using namespace std;
struct node{int u,v,w;
}a[200005];
int f[200005],sz[200005];
int getf(int x){return (f[x]==x)?x:f[x]=getf(f[x]);}
int n,m;
int main(){cin>>n>>m;for(int i=1;i<=m;i++)cin>>a[i].u>>a[i].v>>a[i].w;sort(a+1,a+1+m,[](node x,node y){return x.w<y.w;});for(int i=1;i<=n;i++)f[i]=i,sz[i]=1;int cnt=0,ans=0;while(cnt<n-1){int fu=getf(a[i].u),fv=getf(a[i].v);if(fu!=fv){cnt++;ans+=a[i].w;if(sz[fu]>sz[fv]){f[fv]=fu;sz[fu]+=sz[fv];}else{f[fu]=fv;sz[fv]+=sz[fu];}}cout<<ans;}return 0;
}

3.\(tarjan\) 全家桶

时间复杂度均为:\(O(n)\)

空间复杂度均为:\(O(n)\)

强连通分量:

int n,m,dfn[200005],low[200005],idx,in[200005],id[200005],scc;
stack<int> q;
vector<int> e[200005];
void tarjan(int x){dfn[x]=low[x]=++idx;in[x]=1;q.push(x);for(auto i:e[x])if(!dfn[i]){tarjan(i);low[x]=min(low[x],low[i]);}else if(in[i])low[x]=min(low[x],dfn[i]);if(low[x]==dfn[x]){++scc;int t;do{t=q.top();q.pop();in[t]=0;id[t]=scc;}while(t!=x);}
}

边双连通分量:

int n,m,dfn[200005],low[200005],idx,sz[200005],dcc,id[200005],siz[200005],cnt;
vector<int> e[200005];
stack<int> q;
void tarjan(int x,int fa){dfn[x]=low[x]=++idx;q.push(x);for(auto i:e[x]){if(!dfn[i]){tarjan(i,x),low[x]=min(low[x],low[i]);if(low[i]>dfn[x]){dcc++;int ttt;do{ttt=q.top();q.pop();sz[dcc]++;id[ttt]=dcc;}while(ttt!=i);}}else if(fa!=i)low[x]=min(low[x],dfn[i]);}
}

割点:

 int n,m,rt,cnt,dfn[200005],low[200005],idx;
bitset<200005> fl; 
vector<int> e[20005];
void tarjan(int x){low[x]=dfn[x]=++idx;int cnt=0;for(int i:e[x]){if(!dfn[i]){cnt++;tarjan(i);low[x]=min(low[x],low[i]);if(!fl[x]&&((rt!=x&&dfn[x]<=low[i])||(rt==x&&cnt>1)))::cnt++,fl[x]=1;}else low[x]=min(low[x],dfn[i]);}}

割边:同边双连通分量

4.拓扑排序

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:排出 \(DAG\) 进行 \(dp\) 等操作

#include<bits/stdc++.h>
vector<int> e[200005];
queue<int> q;
int ins[200005];
int n,m;
void top(){while(!q.empty()){int u=q.front();q.pop();for(auto i:e[u]){ins[i]--;if(!ins[i])q.push(i);}}
}
int main(){cin>>n>>m;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;e[x].push_back(y);ins[y]++;}for(int i=1;i<=n;i++)if(!ins[i])q.push(i);top();return 0;
}

5.树的直径

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:树上路径贪心

#include<bits/stdc++.h>
using namespace std;
int n,m;
vector<int> e[200005];
int maxx,u,v;
void dfs(int x,int fa,int d){if(d>maxx)maxx=d,u=x;for(auto i:e[x])if(i!=fa)dfs(i,x,d+1);
}
int main(){cin>>n>>m;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;e[x].push_back(y);e[y].push_back(x);}dfs(1,1,0);v=u;dfs(v,v,0);cout<<u<<" "<<v<<" "<<maxx;return 0;
}

6.树的重心

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:点分治

int n,m,sz[200005];
vector<int> e[200005];
void get_rt(int x,int fa,int sz,int &rt){::sz[x]=1;int maxx=0;for(auto i:e[x])if(i!=fa)get_rt(i,x,sz,rt),::sz[x]+=::sz[i],maxx=max(maxx,::sz[i]);maxx=max(maxx,sz-::sz[x]);if((maxx<<1)<=sz) rt=x;
}

7.点分治

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n\log n)\)

应用:求树上是否存在某种长度路径

#include<bits/stdc++.h>
using namespace std;
namespace fastio{#define il inlineconst int isz=1<<25;char iin[isz],*is=iin+isz,*it=iin+isz;#define gc() (is==it)?(it=(is=iin)+fread(iin,1,isz,stdin),(is==it)?EOF:*is++):*is++template<typename T> il void rd(T &x){x=0;char c=gc();bool fla=false;while(!isdigit(c)) fla|=(c=='-'),c=gc();while(isdigit(c)) x=(x<<1)+(x<<3)+(c&15),c=gc();x=(fla)?-x:x;}template<typename T1,typename...T2> il void rd(T1 &x,T2&...y){rd(x);rd(y...);}template<typename T,typename T1> il void rd(T a[],T1 s,T t){for(T i=s;i<=t;i++) rd(a[i]);}char iout[isz],*ita=iout;#define Flush() fwrite(iout,1,ita-iout,stdout);ita=iouttemplate<typename T> il void wr(T x,char la='\n'){char c[35];int len=0;if(x<0) *ita++='-',x=-x;do{c[++len]=(x%10+'0');x/=10;}while(x);while(len)*ita++=c[len--];*ita++=la;} il void en(char x='\n'){*ita++=x;}
}
using namespace fastio;
const int N=1e7;
bitset<105> ans;
bitset<10005> del;
bitset<N+5> cnt;
int n,m,q[105],sz[10005],dis[10005];
vector<int> e[10005],w[10005],tmp;
void get_rt(int x,int fa,int sz,int &rt){::sz[x]=1;int maxx=0;for(auto i:e[x])if(i!=fa&&!del[i])get_rt(i,x,sz,rt),::sz[x]+=::sz[i],maxx=max(maxx,::sz[i]);maxx=max(maxx,sz-::sz[x]);if((maxx<<1)<=sz) rt=x;
}
void calc(int x,int fa){sz[x]=1;for(int i=0;i<e[x].size();i++)if(e[x][i]!=fa&&!del[e[x][i]]){dis[e[x][i]]=dis[x]+w[x][i];if(dis[e[x][i]]<=N)tmp.push_back(dis[e[x][i]]);calc(e[x][i],x);sz[x]+=sz[e[x][i]];}
}
void solve(int x,int sz){if(sz==1)return;int rt;get_rt(x,x,sz,rt);stack<int> q;del[rt]=::sz[rt]=1;for(int i=0;i<e[rt].size();i++)if(!del[e[rt][i]]){tmp.clear();dis[e[rt][i]]=w[rt][i];tmp.push_back(w[rt][i]);calc(e[rt][i],rt);::sz[rt]+=::sz[e[rt][i]];for(auto j:tmp){q.push(j);for(int k=0;k<m;k++)if(::q[k]-j>=0&&::q[k]-j<=N)ans[k]=(ans[k])|(cnt[::q[k]-j]);}for(auto j:tmp)cnt[j]=1;}while(!q.empty())cnt[q.top()]=0,q.pop();for(auto i:e[rt])if(!del[i])solve(i,::sz[i]);
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);rd(n,m);for(int i=1;i<n;i++){int x,y,z;rd(x,y,z);e[x].push_back(y);e[y].push_back(x);w[x].push_back(z);w[y].push_back(z);}for(int i=0;i<m;i++)rd(q[i]);cnt[0]=1;solve(1,n);for(int i=0;i<m;i++)if(ans[i])cout<<"AYE\n";else cout<<"NAY\n";return 0;
}

8.树上启发式合并

时间复杂度:\(O(n\log^2 n)\)

空间复杂度:\(O(n)\)

应用:通过重链性质维护一些颜色数

#include<bits/stdc++.h>
using namespace std;
namespace FastIO{#define il inlineconst int isz=1<<25;char iin[isz],*is=iin+isz,*it=iin+isz;#define gc() (is==it)?(it=(is=iin)+fread(iin,1,isz,stdin),(it==is)?EOF:*is++):*is++template<typename T> il void rd(T &x){char c=gc();bool fla=false;x=0;while(!isdigit(c)) fla|=(c=='-'),c=gc();while(isdigit(c)) x=(x<<1)+(x<<3)+(c&15),c=gc();x=(fla)?-x:x;} template<typename T1,typename... T2> il void rd(T1 &x,T2 &...y){rd(x);rd(y...);}char iout[isz],*ita=iout;#define Flush() fwrite(iout,1,ita-iout,stdout);ita=iouttemplate<typename T> il void wr(T &x,char la='\n'){char c[35];int ilen=0;if(x<0) x=-x,*ita++='-';do{c[++ilen]=(x%10+'0');x/=10;}while(x);while(ilen) *ita++=c[ilen--];*ita++=la;}template<typename T1,typename...T2> il void wr(T1 &x,T2 &...y){wr(x,' ');wr(y...);}
}
using namespace FastIO;
#define cr(x,y) e[x].push_back(y),e[y].push_back(x)
const int N=2e5+5;
int n,ans,c[N],f[N],cnt[N],sum[N],sz[N],son[N];
vector<int> e[N];
template<typename T> void mins(T &x,T y){if(sz[x]<sz[y])x=y;}
il void dfs(int x){sz[x]=1;for(auto i:e[x]) if(i!=f[x]) dfs(i),sz[x]+=sz[i],mins(son[x],i);
}
void add(int x,int d){--sum[cnt[c[x]]];cnt[c[x]]+=d;sum[cnt[c[x]]]++;for(auto i:e[x]) if(i!=f[x]) add(i,d);
}
il void check(int x,bool fl=false){for(auto i:e[x]) if(i!=son[x]&&i!=f[x]) check(i);if(son[x])check(son[x],true);--sum[cnt[c[x]]];cnt[c[x]]++;sum[cnt[c[x]]]++;for(auto i:e[x]) if(i!=son[x]&&i!=f[x]) add(i,1);if(cnt[c[x]]*sum[cnt[c[x]]]==sz[x]) ++ans;if(!fl) add(x,-1);
}
int main(){rd(n);for(int i=1;i<=n;i++) rd(c[i],f[i]),cr(i,f[i]);dfs(1);check(1,true);wr(ans);Flush();return 0;
}

9.倍增 \(LCA\)

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n\log n)\)

应用:求树上路径

int n,m,f[5005][20],d[5005],w[5005][20];
vector<pair<int,int>>e[5005]; 
void dfs(int x,int fa){for(int i=1;i<=13;i++)f[x][i]=f[f[x][i-1]][i-1],w[x][i]=w[x][i-1]+w[f[x][i-1]][i-1];for(auto i:e[x])if(i.first!=fa){f[i.first][0]=x;d[i.first]=d[x]+1;w[i.first][0]=i.second;dfs(i.first,x);}
}
pair<int,int> lca(int x,int y){if(d[x]>d[y])swap(x,y);int res=0;for(int i=13;i>=0;i--)if(d[f[y][i]]>=d[x])res+=w[y][i],y=f[y][i];if(x==y)return {x,res};for(int i=13;i>=0;i--)if(f[x][i]!=f[y][i])res+=w[x][i]+w[y][i],x=f[x][i],y=f[y][i];return {f[x][0],res+w[x][0]+w[y][0]};
}

10.树上差分

时间复杂度:\(O(n\log n)\)

空间复杂度:\(O(n)\)

值得注意的是,点权差分应该是 d[x]+=a,d[y]+=a,d[lca(x,y)]-=a,d[f[lca(x,y)]]-=a,然而边权差分应该是边权转点权 d[x]+=a,d[y]+=a,d[lca(x,y)]-=2*a

11.网络流

时间复杂度:\(O(n^2m)\)

空间复杂度:\(O(n+m)\)

应用:构造网络流图进行计算

给出 \(dinic\) 的代码:

int n;
int dep[5005],ans=0,flag;
int a[5005][5005];
void dfs(int x){for(int i=0;i<=2*n*n+1;i++){if(a[x][i]&&!dep[i]){dep[i]=dep[x]+1;dfs(i);}}
}
int bfs(int x,int f){if(x==2*n*n+1){flag=true;return f;}for(int i=0;i<=2*n*n+1;i++)if(dep[i]==dep[x]+1&&a[x][i]){int fl;fl=bfs(i,min(f,a[x][i]));if(flag){a[x][i]-=fl;a[i][x]+=fl;return fl;}}return 0;
}
void dinic(){int flow;do{memset(dep,0,sizeof dep);dep[0]=1;dfs(0);flag=0;flow=bfs(0,0x3f3f3f3f);ans+=flow;}while(flow);
}
\(Q.E.D\)

Ⅴ.数学

1.线性筛

时间复杂度:\(O(n)\)

空间复杂度:\(O(n)\)

应用:筛质数,筛积性函数,筛质数个数

int t,pri[1000005],prime[1000005],idx,mul[1000005],phi[1000005];
const int len=1000000;
void init(){mul[1]=1;phi[1]=1;memset(pri,true,sizeof pri);for(int i=2;i<=len;i++){if(pri[i]) {prime[++idx]=i;mul[i]=-1;phi[i]=i-1;}for(int j=1;j<=idx&&i*prime[j]<=len;j++){pri[i*prime[j]]=false;if(i%prime[j]){mul[i*prime[j]]=-mul[i];phi[i*prime[j]]=phi[i]*(prime[j]-1);}else{mul[i*prime[j]]=0;phi[i*prime[j]]=phi[i]*prime[j];break;}}}for(int i=1;i<=len;i++)mul[i]+=mul[i-1],phi[i]+=phi[i-1];
}

2.费马小定理

时间复杂度:\(O(\log n)\)

空间复杂度:\(O(1)\)

应用:求模数为质数时的逆元

\[\large a^{p-1} \equiv 1 \pmod{p}\hspace{0.5em}p\in prime \]

详见数学章快速幂

3.快速幂

时间复杂度:\(O(\log n)\)

空间复杂度:\(O(1)\)

数字:

const int mod=1e9;
int power(int x,int y){if(x==0) return 0;int res=1;while(y){if(y&1)res=res*x%mod;x=x*x%mod;y>>=1;}return res;
}

矩阵:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n;
long long k;
struct node{long long a[105][105];node(){memset(a,0,sizeof(a));}void init(){for(int i=0;i<n;i++)a[i][i]=1;}
}ans,a;
node operator*(node x,node y){node z;for(int i=0;i<n;i++)for(int j=0;j<n;j++)for(int k=0;k<n;k++)z.a[j][k]=(z.a[j][k]+x.a[j][i]*y.a[i][k]%mod)%mod;return z;
}
void power(){ans.init();while(k){if(k&1)ans=ans*a;a=a*a;k>>=1;			}
}
int main(){cin>>n>>k;for(int i=0;i<n;i++)for(int j=0;j<n;j++)cin>>a.a[i][j];power();for(int i=0;i<n;i++){for(int j=0;j<n;j++)cout<<ans.a[i][j]<<" ";cout<<endl;}return 0;
} 

4.\(FWT\)

时间复杂度:\(O(n \log n)\)

空间复杂度:\(O(n)\)

应用:二进制卷积

注意 \(n\neq 2^k\) 时要补 0

#include<bits/stdc++.h>
using namespace std;
namespace fastio{#define il inlineconst int isz=1<<25;char iin[isz],*is=iin+isz,*it=iin+isz;#define gc() (is==it)?(it=(is=iin)+fread(iin,1,isz,stdin),(is==it)?EOF:*is++):*is++template<typename T> il void rd(T &x){x=0;char c=gc();bool fla=false;while(!isdigit(c)) fla|=(c=='-'),c=gc();while(isdigit(c)) x=(x<<1)+(x<<3)+(c&15),c=gc();x=(fla)?-x:x;}template<typename T1,typename...T2> il void rd(T1 &x,T2&...y){rd(x);rd(y...);}template<typename T,typename T1> il void rd(T a[],T1 s,T t){for(T i=s;i<=t;i++) rd(a[i]);}char iout[isz],*ita=iout;#define Flush() fwrite(iout,1,ita-iout,stdout);ita=iouttemplate<typename T> il void wr(T x,char la='\n'){char c[35];int len=0;if(x<0) *ita++='-',x=-x;do{c[++len]=(x%10+'0');x/=10;}while(x);while(len)*ita++=c[len--];*ita++=la;} il void en(char x='\n'){*ita++=x;}
}
using namespace fastio;
#define int long long
const int N=(1<<17)+10,mod=998244353;
int n,a[N],b[N],c[N],d[N];
void cop(){for(int i=0;i<n;i++) c[i]=a[i],d[i]=b[i];}
void times(){for(int i=0;i<n;i++)c[i]=c[i]*d[i]%mod;}
void fwt_or(int a[],int opt){for(int i=1;i<n;i<<=1)for(int j=0;j<n;j+=(i<<1))for(int k=0;k<i;k++)a[j+i+k]=(a[j+k+i]+a[j+k]*opt+mod)%mod;}
void fwt_and(int a[],int opt){for(int i=1;i<n;i<<=1)for(int j=0;j<n;j+=(i<<1))for(int k=0;k<i;k++)a[j+k]=(a[j+k+i]*opt+a[j+k]+mod)%mod;}
void fwt_xor(int a[]){for(int i=1;i<n;i<<=1)for(int j=0;j<n;j+=(i<<1))for(int k=0;k<i;k++){int t=a[j+k];a[j+k]=(a[j+k]+a[j+k+i])%mod;a[i+k+j]=(t-a[i+j+k]+mod)%mod;}}
void ifwt_xor(int a[]){for(int i=1;i<n;i<<=1)for(int j=0;j<n;j+=(i<<1))for(int k=0;k<i;k++){int t=a[j+k];a[j+k]=(a[j+k]+a[j+k+i]+(((a[j+k]+a[j+k+i])%2)?mod:0))/2%mod;a[i+k+j]=(t-a[i+j+k]+mod+(((t-a[i+j+k]+mod)%2)?mod:0))/2%mod;}}
signed main(){cin>>n;n=1<<n;rd(a,0,n-1),rd(b,0,n-1);cop();fwt_or(c,1);fwt_or(d,1);times();fwt_or(c,-1);for(int i=0;i<n;i++)wr(c[i],' ');en();Flush();cop();fwt_and(c,1);fwt_and(d,1);times();fwt_and(c,-1);for(int i=0;i<n;i++)wr(c[i],' ');en();Flush();cop();fwt_xor(c);fwt_xor(d);times();ifwt_xor(c);for(int i=0;i<n;i++)wr(c[i],' ');Flush();return 0;
}

5.\(exgcd\)

时间复杂度:\(O(\log n)\)

空间复杂度:\(O(1)\)

应用:求逆元,求二元一次不定方程

inline ll exgcd(ll a,ll b){if(b==0){x=1;y=0;return a;}ll d=exgcd(b,a%b);ll t=x;x=y;y=t-a/b*y;return d;
}
\(Q.E.D\)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/980099.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

会赢吗

会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢吗会赢…

直接通过electron创建项目

直接通过electron创建项目一、创建原始electron实例demo1 1、创建项目目录demo1 2、通过命令初始化项目配置文件package.json 命令:npm init -y 项目目录下生成packabe.json文件 {"name": "demo1"…

东方博宜OJ 1246:请输出n行的9*9乘法表 ← 嵌套循环

​【题目来源】https://oj.czos.cn/p/1246【题目描述】请从键盘读入一个整数 n,代表有 n 行,输出 n 行的 9*9 乘法表。比如,假设 n=5,则输出如下: 1*1=12*1=2 2*2=43*1=3 3*2=6 3*3=94*1=4 4*2=8 4*3=12 4*4=165*…

使用cnpm(中国镜像源的npm客户端)来安装electron

使用cnpm(中国镜像源的npm客户端)来安装electron要使用cnpm(中国镜像源的npm客户端)来安装electron,你需要先确保已经安装了cnpm。如果你还没有安装cnpm,你可以通过npm来安装它。以下是具体步骤:1. 安装 cnpm 首…

2025年11月电动叉车销售企业避坑指南:市场主流品牌横向对比

在制造业转型升级和绿色物流政策推动下,电动叉车市场需求持续增长。根据中国工程机械工业协会数据,2025年前三季度电动叉车销量同比增长18%,占叉车总销量比例突破65%。许多企业管理者在选购电动叉车时面临诸多考量,…

2025年11月中国电动叉车销售公司推荐榜单:主流品牌综合对比分析

作为物流仓储、制造业企业管理者或采购负责人,您在2025年面临日益严格的环保政策与降本增效的双重压力时,选择可靠的电动叉车供应商成为关键决策。根据中国工程机械工业协会数据显示,2024年中国电动叉车销量占比已突…

详细介绍:Qt样式深度解析

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

文档抽取科技:利用自然语言处理技术自动识别和提取合同、判决书等法律文书中的关键信息,并将其转化为结构化数据

文档抽取科技:利用自然语言处理技术自动识别和提取合同、判决书等法律文书中的关键信息,并将其转化为结构化数据pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; d…

替代模型简化复杂物理仿真任务

本文介绍了利用替代模型技术加速多物理场仿真的创新方法。通过机器学习将复杂物理模型压缩为轻量级版本,实现实时仿真计算,应用于电动汽车电池包模拟和食品储存优化等领域,计算速度提升高达10万倍。工程仿真瘦身实现…

U636459 网格

网格走路问题的多维版本。 我们说过,如果网格是二维的,复杂度可以做到 \(\sqrt {nm}\),这是因为通过根号分治有 DP 和容斥的两种解法,这里都需要运用到。 将所有坏点排序之后 DP 即可。

NOIP day -1 笔记

考前打云斗的模拟赛练一下手,比赛链接 T1 二分答案+差分,每次二分都模拟出一个差分数组来找到要求最严格的值,总时间复杂度为 \(O(n\log(a+k))\)AC Code #include<bits/stdc++.h> #define int long long #def…

2025-11-28 用后端java的架构来类比 NestJS 的各个部分(deepseek)

Java和NestJS在设计理念和架构模式上有很深的渊源。NestJS 本身很大程度上受到了 Spring Boot 和 Angular 的启发。核心概念类比NestJS 部件 Java/Spring 类比 核心职责Module (模块) @Configuration 配置类 / Java 包…

2025-11-28 用前端react的架构来类比 NestJS 的各个部分(deepseek)

好的,这个类比非常棒!用前端 React 的架构来理解 NestJS,可以让你快速建立起对 NestJS 各个核心部件的直观感受。 我们来做这样一个类比:一个 NestJS 应用就像一个复杂的 React 组件树,而它的模块、控制器、服务等…

java真分页查询两个库的数据,合并成一个结果集分页查询

/*** 合并两个库的数据,真分页* @param pageNum 页码(从1开始)* @param pageSize 页大小* @param age 查询条件(示例)* @return 分页结果*/public IPage<UserDTO> mergeUserPage(Integer pageNum, Integer …

【Microsoft Learn】Microsoft Azure 服务 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

U636458 蛇

记录一个 DP 表示匹配信息。 然后枚举开头位置,路径形态只有那么几种,枚举一下即可。 可 pku 那个题差不太多。

Spring AI 代码分析(十)--Spring Boot集成

Spring Boot 集成分析 请关注微信公众号:阿呆-bot 1. 工程结构概览 Spring AI 通过 Spring Boot Starter 和 Auto Configuration 机制,实现了零配置的 AI 应用开发。开发者只需要添加依赖和配置属性,就能使用各种 A…

WSL 迁移发行版位置

1、查看发行版 PS C:\Users\18071> wsl --list --verboseNAME STATE VERSION * Ubuntu-20.04 Stopped 22、必须先关闭 WSL wsl --shutdown3、导出发行版 wsl --export Ubuntu-20.0…

7款AI论文写作辅助必备工具:毕业论文高效完成全攻略

校园论坛上毕业生写论文的求助帖引发共鸣,2025 年 AI 成写论文得力助手。作者试用 7 款 AI 论文写作辅助工具并测评,包括图灵论文 AI 写作助手、巨鲸写作等。各工具功能多样,如一键生成初稿、解析导师意见、处理问卷…

MySQL语法之用alter增加删除列

用MySQL alter table增加、删除或修改字段,设置新字段位置。摘要:用MySQL alter table增加、删除或修改字段,设置新字段位置。 综述在 MySQL 中,ALTER TABLE 语法糖常常被用于修改已经创建表的结构。工作中,经常遇…