用途:
线段树分治通常维护时间段,用于有撤销需求的离线操作。所有询问通常一起处理。所以我们就把动态转化为了静态,方便处理更多事情。
例题
1. 动态图连通性(离线)
- 题意:你要维护一张无向简单图。你被要求加入或删除一条边及查询两个点是否连通。
- 思路:线段树上区间代表时间段,对应时间段节点用vetor维护该时段的边的编号。最后用可撤销并查集遍历,每个点回溯时撤销。
注意:叶子节点代表每个单独的时间点,而根到叶子上维护的所有边正式这个时间点上存在的边,所以叶子处理询问答案。 - 代码:感觉不那么好写
#include<bits/stdc++.h>
using namespace std;
const int N=5005;
const int M=1e6+5;
static char buf[1000000],*p1=buf,*p2=buf,obuf[1000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
template<typename item>
inline void read(register item &x)
{
x=0;register char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
static char cc[10000];
template<typename item>
inline void print(register item x)
{
register long long len=0;
while(x)cc[len++]=x%10+'0',x/=10;
while(len--)putchar(cc[len]);
}
struct query {
int opt,p;
}Q[M];
struct edge {int x,y;}E[M];
vector<int> V[M*4];
struct seg {
int l,r;
}T[M*4];
int ans[M],fa[N],G[N][N],l[M],sz[N];
stack<int> st;
int g_fa(int u) {
if(fa[u]==u) return u;
return g_fa(fa[u]);
}
bool Union(int u,int v) {
int uu=g_fa(u),vv=g_fa(v);
if(uu!=vv) {
if(sz[uu]>sz[vv]) swap(uu,vv);
st.push(uu);
sz[vv]+=sz[uu],fa[uu]=vv;
return true;
}
return false;
}
void _past() {
int u=st.top(); st.pop();
sz[g_fa(u)]-=sz[u];
fa[u]=u;
}
void Build(int x,int l,int r) {
T[x].l=l,T[x].r=r;
if(l==r)return;
int mid=(l+r)>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
}
void Update(int x,int l,int r,int e) {
if(l<=T[x].l&&T[x].r<=r) {V[x].push_back(e);return;}
int mid=(T[x].l+T[x].r)>>1;
if(l<=mid) Update(x<<1,l,r,e);
if(r>mid) Update(x<<1|1,l,r,e);
}
void dfs(int x) {
int cnt=0;
for(int i=0;i<V[x].size();i++) {
int e=V[x][i];
cnt+=Union(E[e].x,E[e].y);
}
if(T[x].l==T[x].r) {
int u=Q[T[x].l].p;
if(Q[T[x].l].opt==2) {
ans[T[x].l]=(g_fa(E[u].x)==g_fa(E[u].y));
}
}
if(T[x].l!=T[x].r) dfs(x<<1),dfs(x<<1|1);
for(int i=0;i<cnt;i++) _past();
}
int main() {
int n,m,cc=0;
read(n),read(m);
for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
for(int i=1;i<=m;i++) {
int x,y;
read(Q[i].opt),read(x),read(y);
if(x>y) swap(x,y);
if(!G[x][y]) {G[x][y]=++cc; E[cc]=(edge){x,y};}
Q[i].p=G[x][y];
}
Build(1,1,m);
for(int i=m;i>=1;i--) {
int k=Q[i].p;
if(!Q[i].opt) {
if(!l[k]) Update(1,i,m,k);
else Update(1,i,l[k],k);
}
else if(Q[i].opt==1) l[k]=i;
}
dfs(1);
bool flag=1;
for(int i=1;i<=m;i++) {
if(Q[i].opt==2) {
if(ans[i]) printf("Y\n");
else printf("N\n");
flag=0;
}
}
if(flag) printf("\n");
return 0;
fwrite(obuf,p3-obuf,1,stdout);
}
2.二分图
- 题意:一个无向图,T时间内一些边会出现或者消失,询问某时刻该图是否为二分图。
- 思路:二分图判定:(复习)
1.环的长度为偶数
2.并查集维护点的矛盾关系。一条边意义为两端点不在同一点集。
我选择用方法2,然后就跟例1一模一样啦。
3.花团
- 题意:物品会加入或者退出。给你V,询问体积和为V的物品的价值和的最大值。
- 思路:01背包那味。
这道题解法会有区别的:首先这个题要在线,不过并不影响。还是线段树维护时间段内的物品编号。然后每次询问,提取根到该叶子的信息最后跑01背包。
这时间是会爆的,因为每个线段树上的点都可能被重复算过。
如果一个线段树上的节点在查询的时候被经过过,又因为它之后也不会再被修改,我们就可以再第一次经过时,保存它的信息并打上标记之后不会重新查询。
保存信息按层保存,很棒的方法这样空间就是。
综上时间复杂度为:
这题也恶心,这是跑不满的,因此就玄过了。 - 代码(还好)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int M=21;
int dp[M][N];
int q,maxv,k,lans=0;
bool mark[N*4];
struct node {int v,w;}a[N];
struct seg {
int l,r;
}T[N*4];
vector<int> V[N*4];
void Build(int x,int l,int r) {
T[x].l=l,T[x].r=r;
if(l==r) return;
int mid=(l+r)>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
}
void Update(int x,int y,int l,int r) {
if(l<=T[x].l&&T[x].r<=r) {V[x].push_back(y);return;}
int mid=(T[x].l+T[x].r)>>1;
if(l<=mid) Update(x<<1,y,l,r);
if(r>mid) Update(x<<1|1,y,l,r);
}
int Ask(int d,int x,int y,int sv) {
if(!mark[x]) {
for(int j=0;j<=maxv;j++) dp[d][j]=dp[d-1][j];
for(int j=0;j<V[x].size();j++) {
int u=V[x][j];
for(int j=maxv;j>=a[u].v;j--) {
dp[d][j]=max(dp[d][j],dp[d][j-a[u].v]+a[u].w);
}
}
mark[x]=1;
}
if(T[x].l==T[x].r) return dp[d][sv];
int mid=(T[x].l+T[x].r)>>1;
if(y<=mid) return Ask(d+1,x<<1,y,sv);
else return Ask(d+1,x<<1|1,y,sv);
}
int main() {
scanf("%d%d%d",&q,&maxv,&k);
memset(dp,-0x3f,sizeof(dp));
for(int i=0;i<M;i++) dp[i][0]=0;
// printf("!");
Build(1,1,q);
for(int i=1;i<=q;i++) {
int opt,v,w,e;
scanf("%d%d",&opt,&v);
v-=lans*k;
if(opt==1) {
scanf("%d%d",&w,&e);
w-=lans*k,e-=lans*k;
a[i]=(node){v,w};
Update(1,i,i,e);
}
else {
int ans=Ask(1,1,i,v);
if(ans<0) printf("0 0\n"),lans=0;
else printf("1 %d\n",ans),lans=1^ans;
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人