BZOJ3123: [Sdoi2013]森林
Description
小Z有一片森林,含有N个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有M条边。
小Z希望执行T个操作,操作有两类:
1. Q x y k
查询点x到点y路径上所有的权值中,第k小的权值是多少。此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
2. L x y
在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。
为了体现程序的在线性,我们把输入数据进行了加密。设lastans为程序上一次输出的结果,初始的时候lastans为0。
对于一个输入的操作Q x y k
,其真实操作为Q x^lastans y^lastans k^lastans
。
对于一个输入的操作L x y
,其真实操作为L x^lastans y^lastans
。其中^运算符表示异或,等价于pascal中的xor运算符。
请写一个程序來帮助小Z完成这些操作。
对于所有的数据,n,m,T<= 8*10^48∗104 .
Input
第一行包含一个正整数testcase,表示当前测试数据的测试点编号。保证1<=testcase<=20。
第二行包含三个整数N,M,T,分别表示节点数、初始边数、操作数。
第三行包含N个非负整数表示 N个节点上的权值。
接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边。
接下来 T行,每行描述一个操作,格式为”Q x y k“或者”L x y “,其含义见题目描述部分。
Output
对于每一个第一类操作,输出一个非负整数表示答案。
Sample Input
8 4 8
1 1 2 2 3 3 4 4
4 7
1 8
2 4
2 1
Q 8 7 3
Q 10 0 0
L 5 4
L 3 2
Q 9 2 5
Sample Output
2
1
4
2
HINT
对于第一个操作 Q 8 7 3,此时 lastans=0,所以真实操作为Q 8^0 7^0 3^0,也即Q 8 7 3。点8到点7的路径上一共有5个点,其权值为4 1 1 2 4。
这些权值中,第三小的为 2,输出 2,lastans变为2。
对于第二个操作 Q 3 5 1 ,此时lastans=2,所以真实操作为Q 3^2 5^2 1^2 ,也即Q 1 7 3。点1到点7的路径上一共有4个点,其权值为 1 1 2 4 。
这些权值中,第三小的为2,输出2,lastans变为 2。之后的操作类似。
题解Here!
首先看到第k小,立马想到主席树!
于是树上第k小就这么愉快的解决了: 对 u,v,LCA(u,v),fa[ LCA(u,v) ] 整体处理——
1 2 3 4 5 6 | int query( int u, int v, int f, int gf, int k, int l, int r){ if (l==r) return l; int mid=l+r>>1,t=a[a[u].l].sum+a[a[v].l].sum-a[a[f].l].sum-a[a[gf].l].sum; if (k<=t) return query(a[u].l,a[v].l,a[f].l,a[gf].l,k,l,mid); else return query(a[u].r,a[v].r,a[f].r,a[gf].r,k-t,mid+1,r); } |
插入主席树时,用父节点的主席树更新此节点的主席树——
1 2 3 4 5 6 7 8 9 10 11 12 | void insert( int k, int l, int r, int &rt, int fa){ int mid; rt=++size;a[rt]=a[fa]; if (l==k&&k==r){ a[rt].sum++; return ; } mid=l+r>>1; if (k<=mid)insert(k,l,mid,a[rt].l,a[fa].l); else insert(k,mid+1,r,a[rt].r,a[fa].r); pushup(rt); } |
但是有个连边操作很烦人。。。
两个节点连边,就是两棵子树的合并。
主席树当然可以做到 O(logn) 时间内启发式合并,但是LCA怎么办?又不能启发式。。。
等等,不能启发式就暴力 dfs 啊!
时限 2s ,显然可以卡一卡。。。
由于有修改,LCA当然选倍增。
于是,一个带倍增处理的暴力 dfs 破空而出——
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void buildtree( int rt, int fa, int ancestry){ f[rt][0]=fa; for ( int i=1;i<=19;i++)f[rt][i]=f[f[rt][i-1]][i-1]; size[ancestry]++; deep[rt]=deep[fa]+1; father[rt]=fa; int x=lower_bound(num+1,num+p+1,val[rt])-num; ST::insert(x,1,p,root[rt],root[fa]); for ( int i=head[rt];i;i=a[i].next){ int will=a[i].to; if (will==fa) continue ; buildtree(will,rt,ancestry); } } |
然后那个倍增 LCA 就不多说了。
注意:
1. 记得离散化!记得离散化!记得离散化!
2. 那个 testcase 是测试点编号!不是组数!也就是为了方便我们这些蒟蒻打打暴力。。。
3. 记得优化一下常数,什么 inline 啦,快速读入啦乱搞一波。。。
附代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #include<iostream> #include<algorithm> #include<cstdio> #define MAXN 80010 using namespace std; int n,m,q,p,c=1; int val[MAXN],num[MAXN],root[MAXN]; int head[MAXN],deep[MAXN],size[MAXN],f[MAXN][20],father[MAXN]; struct Edge{ int next,to; }a[MAXN<<2]; inline int read(){ int date=0,w=1; char c=0; while (c< '0' ||c> '9' ){ if (c== '-' )w=-1;c= getchar ();} while (c>= '0' &&c<= '9' ){date=date*10+c- '0' ;c= getchar ();} return date*w; } namespace ST{ int size=0; struct Segment{ int l,r,sum; }a[MAXN*400]; inline void pushup( int rt){ a[rt].sum=a[a[rt].l].sum+a[a[rt].r].sum; } inline void buildtree(){ a[0].l=a[0].r=a[0].sum=root[0]=0; } void insert( int k, int l, int r, int &rt, int fa){ int mid; rt=++size;a[rt]=a[fa]; if (l==k&&k==r){ a[rt].sum++; return ; } mid=l+r>>1; if (k<=mid)insert(k,l,mid,a[rt].l,a[fa].l); else insert(k,mid+1,r,a[rt].r,a[fa].r); pushup(rt); } int query( int u, int v, int f, int gf, int k, int l, int r){ if (l==r) return l; int mid=l+r>>1,t=a[a[u].l].sum+a[a[v].l].sum-a[a[f].l].sum-a[a[gf].l].sum; if (k<=t) return query(a[u].l,a[v].l,a[f].l,a[gf].l,k,l,mid); else return query(a[u].r,a[v].r,a[f].r,a[gf].r,k-t,mid+1,r); } } inline int find( int x){ return father[x]==x?x:father[x]=find(father[x]);} inline void add( int x, int y){ a[c].to=y;a[c].next=head[x];head[x]=c++; a[c].to=x;a[c].next=head[y];head[y]=c++; } void buildtree( int rt, int fa, int ancestry){ f[rt][0]=fa; for ( int i=1;i<=19;i++)f[rt][i]=f[f[rt][i-1]][i-1]; size[ancestry]++; deep[rt]=deep[fa]+1; father[rt]=fa; int x=lower_bound(num+1,num+p+1,val[rt])-num; ST::insert(x,1,p,root[rt],root[fa]); for ( int i=head[rt];i;i=a[i].next){ int will=a[i].to; if (will==fa) continue ; buildtree(will,rt,ancestry); } } int LCA( int x, int y){ if (deep[x]<deep[y])swap(x,y); for ( int i=19;i>=0;i--) if (deep[f[x][i]]>=deep[y])x=f[x][i]; if (x==y) return x; for ( int i=19;i>=0;i--) if (f[x][i]!=f[y][i]){x=f[x][i];y=f[y][i];} return f[x][0]; } void work(){ char ch[2]; int x,y,k,last=0; while (q--){ scanf ( "%s" ,ch);x=read()^last;y=read()^last; if (ch[0]== 'Q' ){ k=read()^last; int fa=LCA(x,y); last=num[ST::query(root[x],root[y],root[fa],root[f[fa][0]],k,1,p)]; printf ( "%d\n" ,last); } if (ch[0]== 'L' ){ add(x,y); int fx=find(x),fy=find(y); if (size[fx]<size[fy]){ swap(x,y); swap(fx,fy); } buildtree(y,x,fx); } } } void init(){ int x,y,cases=read(); n=read();m=read();q=read(); for ( int i=1;i<=n;i++){ num[i]=val[i]=read(); father[i]=i; } for ( int i=1;i<=m;i++){ x=read();y=read(); add(x,y); } sort(num+1,num+n+1); p=unique(num+1,num+n+1)-num-1; ST::buildtree(); for ( int i=1;i<=n;i++) if (!deep[i]){buildtree(i,0,i);father[i]=i;} } int main(){ init(); work(); return 0; } |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步