2025 CSP/NOIp 复习
其实是重新学习(擦汗
1.Manacher
点击查看代码
//manacher板子 求子串最长回文串的长度
#include<bits/stdc++.h>
using namespace std;
const int maxn=1.1e7+10;
string s;
int pal[2*maxn],ans;
//pal表示以第i个字符为中心的最大回文半径 不包括中心字符本身
//所以长度为奇数or偶数的回文串都可以处理
string get(string s){
string t="$#";
for(int i=0;i<s.size();i++){
t+=s[i];
t+="#";
}
t+="@";
return t;
}
void manacher(string s){
s=get(s);
int len=s.size();
int maxr=0,pos=0;
//pos表示当前回文中心的位置 maxr是当前回文右边界的位置
for(int i=1;i<len;i++){
if(i<maxr) pal[i]=min(pal[2*pos-i],maxr-i+1);
else pal[i]=1;
while(s[i+pal[i]]==s[i-pal[i]]) pal[i]++;
if(pal[i]+i-1>=maxr){
maxr=pal[i]+i-1;
pos=i;
}
if(ans<pal[i]-1) ans=pal[i]-1;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>s;
manacher(s);
cout<<ans<<'\n';
}
2.ST 表
点击查看代码
//一眼RMQ(区间最值问题) ST表+倍增
//f[i][j]表示区间(i,i+2^j-1)的最大值
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int n,q,a,b;
int logn[maxn],f[maxn][25],f2[maxn][25];
//对于每个询问a,b,我们把它分为两部分,[l,l+2^s-1] [r-2^s+1,r]
//其中s=log[r-l+1]
/*关于log 怎么进行预处理?
log[1]=0;
log[i]=log[i/2]+1
*/
void pre(){
logn[1]=0;
logn[2]=1;
for(int i=3;i<=2e5+10;i++){
logn[i]=logn[i/2]+1;
}
}
int s,x,y;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>f[i][0];
// f2[i][0]=f[i][0];
}
// pre();
for(int j=1;j<=21;j++){
for(int i=1;i+(1<<j)-1<=n;i++){//1<<j为2^j次方 倍增思想
f[i][j]=max(f[i][j-1],f [i+(1<<(j-1))] [j-1]);
}
}
for(int i=1;i<=q;i++){
cin>>x>>y;
s=log2(y-x+1);
int d=max(f[x][s],f[y-(1<<s)+1][s]);
cout<<d<<"\n";
}
}
3.LCA
1)树剖求LCA
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
int n,m,s;
struct edge{
int to,nxt;
}e[2*maxn];
int head[maxn],edgenum;
void add_edge(int u,int v){
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int f[maxn],dep[maxn],siz[maxn],son[maxn],dfn[maxn],cnt;
int top[maxn];
void dfs(int u,int fa){
siz[u]=1,f[u]=fa;
dep[u]=dep[fa]+1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++cnt;
if(!son[u]) return;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==f[u] || v==son[u]) continue;
dfs2(v,v);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=f[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>s;
for(int i=1,x,y;i<n;i++){
cin>>x>>y;
add_edge(x,y),add_edge(y,x);
}
dfs(s,0);
dfs2(s,s);
// return 0;
while(m--){
int a,b;
cin>>a>>b;
cout<<lca(a,b)<<'\n';
}
}
2)倍增
点击查看代码
int dep[maxn],f[maxn][22];
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
for(int i=0;i<=19;i++)
f[u][i+1]=f[f[u][i]][i];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue;
f[v][0]=u;
dfs(v,u);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
}
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int getdis(int x,int y){
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
3)x-->y 走k步
点击查看代码
int jump(int x,int y,int k){//x往y走k步到达的节点
int Lca=lca(x,y);
if(getdis(x,y)<k) return 0;
else if(getdis(x,y)==k) return y;
if(getdis(x,Lca)>=k){
while(k!=0){
int t=__lg(k);
x=f[x][t];
k-=pow(2,t);
}
return x;
}
else{
k-=(getdis(x,Lca));
k=getdis(y,Lca)-k;
while(k!=0){
int t=__lg(k);
y=f[y][t];
k-=pow(2,t);
}
return y;
}
}
4)k级祖先
点击查看代码
int getk(int u,int k){
for(int i=0;k;k>>=1,i++){
if(k&1) u=f[u][i];
}
return u;
}
4.最短路
1)堆优化dij
点击查看代码
bool vis[maxn];
int dis[maxn];
struct node{
int dis,pos;
bool operator<(const node &x)const{
return x.dis<dis;
}
};
void dij(int x){
memset(vis,0,sizeof(vis));
memset(dis,0x7f7f7f7f,sizeof(dis));
priority_queue<node> q;
dis[x]=0;
q.push((node){0,x});
while(!q.empty()){
node tmp=q.top();
q.pop();
int u=tmp.pos,d=tmp.dis;
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(!vis[v]) q.push((node){dis[v],v});
}
}
}
}
2)floyd
点击查看代码
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]=min(dp[i][k]+dp[k][j],dp[i][j]);
}
}
}
5.字符串相关
1)hash
点击查看代码
ull p[maxn],h[maxn];
p[0]=1;
for(int i=1;i<maxn;i++){
p[i]=p[i-1]*base;
}
for(int i=1;i<=n;i++){
h[i]=h[i-1]*base+(ull)s[i]-'a';
}
//求lr区间的hash值
ull gethash(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}
2)KMP
点击查看代码
#include<bits/stdc++.h>
using namespace std;
string a,b;
int la,lb,nxt[1000005];
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>a>>b;
la=a.size(),lb=b.size();
a=" "+a,b=" "+b;
for(int i=2,j=0;i<=lb;i++){
while(j && b[i]!=b[j+1])
j=nxt[j];
if(b[j+1]==b[i])
j++;
nxt[i]=j;
}
for(int i=1,j=0;i<=la;i++){
while(j>0 && b[j+1]!=a[i])
j=nxt[j];
if(b[j+1]==a[i]) j++;
if(j==lb){
cout<<i-lb+1<<'\n';
j=nxt[j];
}
}
for(int i=1;i<=lb;i++) cout<<nxt[i]<<" ";
}
6.线性筛
点击查看代码
vis[1]=1;
for(int i=2;i<=100000;i++){
if(!vis[i]) p[++cnt]=i;
for(int j=1;j<=cnt && p[j]*i<=100000;j++){
vis[p[j]*i]=1;
if(i%p[j]==0) break;
}
}
7.离散化
点击查看代码
for(int i=1;i<=n;i++){
lsh[i]=a[i];
}
sort(lsh+1,lsh+1+n);
m=unique(lsh+1,lsh+1+n)-lsh-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(lsh+1,lsh+1+m,a[i])-lsh;
}
8.最小生成树
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,fa[5005],cnt,ans;
struct node{
int u,v,w;
}a[200005];
bool cmp(node p,node q){
return p.w<q.w;
}
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);//并查集的基础上
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
cin>>a[i].u>>a[i].v>>a[i].w;
}
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;i++){
int t1,t2;
t1=find(a[i].u);
t2=find(a[i].v);
if(t1!=t2){
fa[t1]=t2;
cnt++;
ans+=a[i].w;
}
if(cnt==n-1) break;
}
if(cnt==n-1) cout<<ans;
else cout<<"orz";//判负权环
return 0;
}
9.topo排序
点击查看代码
void toposort(){
queue<int>q;
for(int i=1;i<=n;i++) {
if(in[i]==0){ //应提前预处理此数组
q.push(i);
dep[i]=1;
}
}
while(!q.empty()){
int x=q.front();
q.pop();
cout<<x<<" ";
for(int i=head[x];i;i=e[i].nxt) {
int v=e[i].to;
dep[v]=dep[x]+1; //记录顺序
ans=max(ans,dep[v]);
in[v]--; //一定要记得入度减
if(in[v]==0) q.push(v);
}
}
}
10.dfs序 欧拉序
11.tarjan全家桶
1)强连通分量
2)缩点
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=10015;
int n,m,a[maxn],s;
struct edge{
int to,nxt,fr;
}e[10*maxn],ed[10*maxn];
int head[maxn],edgenum;
void add_edge(int u,int v){
e[++edgenum].nxt=head[u];
e[edgenum].fr=u;
e[edgenum].to=v;
head[u]=edgenum;
}
int low[maxn],dfn[maxn],tim,vis[maxn],vol[maxn];
stack<int> st;
void tarjan(int x){
low[x]=dfn[x]=++tim;
st.push(x);
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
int y;
while(y=st.top()){
st.pop();
vol[y]=x;
vis[y]=0;
if(x==y) break;
a[x]+=a[y];
}
}
}
int h[maxn],in[maxn],dis[maxn];
int topo(){
queue<int> q;
int tot=0;
for(int i=1;i<=n;i++){
if(vol[i]==i && !in[i]){
q.push(i);
dis[i]=a[i];
}
}
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=h[x];i;i=ed[i].nxt){
int v=ed[i].to;
dis[v]=max(dis[v],dis[x]+a[v]);
in[v]--;
if(in[v]==0) q.push(v);
}
}
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,dis[i]);
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
add_edge(u,v);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=m;i++){
int x=vol[e[i].fr],y=vol[e[i].to];
if(x!=y){
ed[++s].nxt=h[x];
ed[s].to=y;
h[x]=s;
in[y]++;
}
}
cout<<topo()<<endl;
}
3)边双
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
int n,m;
struct edge{
int to,nxt;
}e[2*maxn];
int head[maxn],edgenum=1;
void add_edge(int u,int v){
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int dfn[maxn],low[maxn],tim,cnt,col[maxn];
int st[maxn],top;
bool b[2*maxn];//i是否为割边
int tot[maxn];
vector<int> ans[maxn];
stack<int> s;
void tarjan(int u,int fa){
dfn[u]=low[u]=++tim;
s.push(u);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]){
b[i]=b[i^1]=1;//该边及其反向边均为桥
}
}
else if(i!=(fa^1))
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
++cnt;
while(1){
int t=s.top();
s.pop();
col[t]=cnt;
if(u==t) break;
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
add_edge(u,v);
add_edge(v,u);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i,0);
}
cout<<cnt<<endl;
for(int i=1;i<=n;i++){
tot[col[i]]++;
ans[col[i]].push_back(i);
}
int now=1;
while(tot[now]){
cout<<tot[now]<<" ";
for(int i=0;i<ans[now].size();i++)
cout<<ans[now][i]<<" ";
now++;
cout<<endl;
}
}
12.dp 相关
其实我只会朴素01背包朴素LIS一点点状压,你说得对但我考场上真能推出来 dp 方程吗
1)线性 dp
通常设 dp[i][j] 表示前i个,以j结尾的xx最大值,如果第一位没用的话可以滚掉,时间复杂度 n^2,如果dp式子没问题考虑优化的话,想单调性,数据结构
2)区间dp
通常设 dp[i][j] 表示i--j这个区间的贡献,然后枚举断点,合并求值。
点击查看代码
for(int len=1;len<=n;len++){
for(int i=1;i<=n-len+1;i++){
int j=i+len-1;
for(int k=i;k<j;k++){
//状态转移方程
}
}
}
然后套路:
如果是枚举断点or合并的话,一般都有dp[i][j]=dp[i][k]+dp[k][j]
然后还可以向两边扩展,比如 dp[i][j]=max/min(dp[i+1][j]+a[i],dp[i][j-1]+a[j],dp[i+1][j-1]+a[i]+a[j])
转移的时候注意分类讨论,是否合法以及是否越界
如果实在难以维护,考虑多开一维or多开一个数组,辅助转移
优化时间复杂度可以考虑前缀和or差分
3)背包dp
这个非常简单嗷,经典背包九讲
01背包
#include<bits/stdc++.h>
using namespace std;
int t,m;
int tim[105],val[105];
int dp[1005];
int ans=-1;
int main(){
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>tim[i]>>val[i];
}
for(int i=1;i<=m;i++){
for(int j=t;j>=0;j--){
if(j>=tim[i])
dp[j]=max(dp[j],dp[j-tim[i]]+val[i]);
}
}
for(int i=1;i<=t;i++){
ans=max(dp[i],ans);
}
cout<<ans<<endl;
return 0;
}
完全背包
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[105],w[105];
int dp[305];
int ans=-1;
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=m;i++){
for(int j=v[i];j<=n;j++){//相比于01背包,这个是从小到大枚举的
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<"max="<<dp[n]<<endl;
return 0;
}
分组背包
#include<bits/stdc++.h>
using namespace std;
int w[10005],q[10005],p[1000][1000],dp[10005];
int main(){
int s,v,n,t;
cin>>v>>n>>t;
for(int i=1;i<=n;i++){
cin>>w[i]>>q[i]>>s;
p[s][0]++;
p[s][p[s][0]]=i;
}
for(int i=1;i<=t;i++){
for(int j=v;j>=0;j--){
for(int k=1;k<=p[i][0];k++){
if(j>=w[p[i][k]])
dp[j]=max(dp[j],dp[j-w[p[i][k]]]+q[p[i][k]]);
}
}
}
cout<<dp[v]<<endl;
return 0;
}
哦哦还有树上背包
点击查看代码
#include<iostream>
#include<cstdio>
#define maxn 200
#define maxm 400
using namespace std;
int n,v,cnt,w[maxn],c[maxn],dp[maxn][maxm];
//dp[u][i]表示从以u为根的子树中选,总体积不超过j的max价值
int root;
int head[maxn],dis[maxn],vis[maxn];
struct node{
int to,next;
}e[maxm];
// 链式前向星 或者叫 邻接表
//加边操作
void add(int x,int y){
cnt++;
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt;
}
void dfs(int k){ //当前节点k
for(int i=head[k];i;i=e[i].next){// 枚举物品
int son=e[i].to; // 记录子节点
dfs(son);// 向下递归到最末子树 在回溯的过程中从最末更新dp值 直到回到root
// 由于当前节点k必选 因此体积j需要将c[k]空出来 01背包倒序枚举体积
for(int j=v-c[k];j>=0;j--){
for(int l=0;l<=j;l++){// 枚举决策
dp[k][j]=max(dp[k][j],dp[k][j-l]+dp[son][l]);
}// 不选son子树 选son子树
}
}
for(int i=v;i>=c[k];i--) dp[k][i]=dp[k][i-c[k]]+w[k];
for(int i=0;i<c[k];i++) dp[k][i]=0;
}
int main(){
scanf("%d%d",&n,&v);
for(int i=1;i<=n;i++){
int p;
scanf("%d%d%d",&c[i],&w[i],&p);
if(p==-1) root=i; // 根节点
add(p,i); // 加边加边 由父节点指向子节点
}
dfs(root); // 从根节点开始搜
printf("%d",dp[root][v]);
}
4)状压dp
特点是数据范围很小,可以用来骗分(,经常把状态压成二进制位(eg:0/1表示不选or选。dp方程看自己发挥吧。。
5)树形dp
改题的时候发现自己不会写线段树上二分,存一个给自己看
树状数组求逆序对!
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e5+10;
int n,a[maxn],d[maxn],ans;
int tr[maxn<<2];
int lowbit(int x){
return x&(-x);
}
void add(int x,int val){
for(int i=x;i<=n;i+=lowbit(i)){
tr[i]+=val;
}
}
int query(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=tr[i];
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],d[i]=a[i];
sort(a+1,a+1+n);
int tmp=unique(a+1,a+1+n)-a-1;
for(int i=1;i<=n;i++){
d[i]=lower_bound(a+1,a+1+tmp,d[i])-a;
}
for(int i=n;i>=1;i--){
add(d[i],1);
ans+=query(d[i]-1);
}
cout<<ans<<endl;
}
“当蓝色的夜坠落在世界时,没人看见我们手牵着手。”

浙公网安备 33010602011771号