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

树形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;
}
posted @ 2025-10-31 11:04  Aapwp  阅读(28)  评论(1)    收藏  举报
我给你一个没有信仰的人的忠诚