树这个结构是真的神奇,很多算法和复杂一点的数据结构,都是以树为基础的,
因为树结构的可以再很快的时间(logn)去解决很多问题。
比如 去做一个dfs搜索,实际上就是一个状态空间上的搜索树。
然后就是线段树、平衡树、动态树、Trie树(前缀树)。都用来解决一些特殊的问题。
下面从头开是讲树
大部分数据结构书上都说了,总结一下,就是1对n,
我们一般用树的根来表示一棵树(标号), 比如0是根
那么0是一棵树, 如果0->1, 0->2, 2->3,
那么1,2也是一棵树, 同时也是0的两颗子树
父指针
fa[N];
儿子指针(用于M叉树)
son[N][M];
儿子兄弟指针(其实和图的邻接表一样,用于一般的非多叉树和森林);
直径
题目poj1849
#include
#include
#include
#define rep(i,l,r) for(int i=l;i
#define Rep(i,l,r) for(int i=l;i<=r;i++)
#define rrep(i,l,r) for(int i=r;i>=l;i--)
#define lc rt<<1
#define lson l,m,rt<<1
#define rc rt<<1|1
#define rson m+1,r,rt<<1|1
#define mp make_pair
#define pb push_back
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+10;
vector<pii> G[N];
int d,n,s,maxDep,lp,rp,ans;
bool vis[N];
int dep[N];
void dfs(int u){
vis[u]=true;
if(dep[u]>maxDep){
maxDep=dep[u];
lp=u;
}
int sz=G[u].size();
int v;
rep(i,0,sz){
v=G[u][i].second;
if(!vis[v]){
dep[v]=dep[u]+G[u][i].first;
dfs(v);
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios::sync_with_stdio(false);
while(cin>>n>>s){
int x,y,v;
ans=0;
Rep(i,1,n)G[i].clear();
rep(i,0,n-1){
cin>>x>>y>>v;
ans+=v;
G[x].pb(mp(v,y));
G[y].pb(mp(v,x));
}
ans*=2;
//树的直径dfs两遍
memset(vis,false,sizeof vis);
maxDep=-1,lp=-1;
dep[1]=0;
dfs(1);
memset(vis,false,sizeof vis);
int tp=lp;
maxDep=-1,lp=-1;
dep[tp]=0;
dfs(tp);
//dep[lp]为直径
ans-=dep[lp];
cout<<ans<<endl;
}
return 0;
}
重心
题目poj1655
int dp[N];
void dfs(int u){
vis[u]=true;dp[u]=1;int sz=G[u].size();
rep(i,0,sz){
int v=G[u][i];
int res=0;
if(!vis[v]){
dfs(v);
dp[u]+=dp[v];
res=max(res,dp[v])
}
}
res=max(res,n-dp[u]);
if(res<mmin){
mmin=res;
center=u;
}
}
点分治
poj 1741
点分治, 就是将重心作为跟来进行DFS,以此来解决计数问题,
计数问题往往也是通过DP的方法求解。
struct UFSet{
static const int N=1e3+10;
int fa[N],rnk[N];
void init(){memset(fa,-1,sizeof fa);memset(rnk,0,sizeof rnk);}
int Find(int x){return fa[x]==-1?x:fa[x]=Find(fa[x]);}
//void init(){for(int i=1;i
//int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
int Union(int x,int y){
int r1=Find(x),r2=Find(y);
if(r1==r2)return 0;
if(rnk[r1]>rnk[r2]) fa[r2]=r1;
else{
fa[r1]=r2;
if(rnk[r1]==rnk[r2])rnk[r2]++;
}
return 1;
}
};
【uva11987】带删除的并查集
题意:初始有N个集合,分别为 1 ,2 ,3 …n。有三种操件
1 p q 合并元素p和q的集合
2 p q 把p元素移到q集合中
3 p 输出p元素集合的个数及全部元素的和。
因为只是维护集合关系,而不是图的连通关系,所以可以直接建立一个新的点
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int N=10*100100;
int n,m,tot,id[N],fa[N],cnt[N];
LL sum[N];
int findfa(int x)
{
if(fa[x]==x) return x;
return findfa(fa[x]);
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++) id[i]=i,fa[i]=i,cnt[i]=1,sum[i]=i;
tot=n;
for(int i=1;i<=m;i++)
{
int tmp,x,y,xx,yy;
scanf("%d",&tmp);
if(tmp==1)
{
scanf("%d%d",&x,&y);
xx=findfa(id[x]);yy=findfa(id[y]);
if(xx==yy) continue;//debug
fa[xx]=yy;
sum[yy]+=sum[xx];
cnt[yy]+=cnt[xx];
sum[xx]=0;cnt[xx]=0;
}
if(tmp==2)
{
scanf("%d%d",&x,&y);
xx=findfa(id[x]);yy=findfa(id[y]);
tot++;
id[x]=tot;
fa[tot]=yy;
sum[yy]+=(LL)x;
cnt[yy]++;
sum[xx]-=(LL)x;
cnt[xx]--;
}
if(tmp==3)
{
scanf("%d",&x);
xx=findfa(id[x]);
printf("%d %lld\n",cnt[xx],sum[xx]);
}
// for(int j=1;j<=n;j++)
// {
// printf("%d fa = %d cnt = %d sum = %d\n",j,fa[id[j]],cnt[id[j]],sum[id[j]]);
// }
// printf("\n");
}
}
return 0;
}
树链剖分就使用到了DFS序, 以及重链序
struct BIT{
ll c[N];
int len;
int lbt(int x){return x&(-x);}
void init(int n){len=n;memset(c,0,sizeof c);}
void upd(int x,int v){
while(x<=n){
c[x]+=v;
x+=lbt(x);
}
}
ll query(int x){
ll ret=0;
while(x>0){
ret+=c[x];
x-=lbt(x);
}
return ret;
}
};
#include
#include
#include
#include
using namespace std;
typedef long long ll;
int n,M,P;
char op[10];
int l,r,v;
//树链剖分
const int MAXN = 50010;
struct Edge
{
int to,next;
}edge[MAXN*2];
int head[MAXN],tot;
int top[MAXN];//top[v] 表示v所在的重链的顶端节点
int faz[MAXN];//父亲节点
int dep[MAXN];//深度
int num[MAXN];//num[v] 表示以v为根的子树的节点数
int pid[MAXN];//pid[v]表示v对应的位置
int fpid[MAXN];//fpid和pid数组相反
int son[MAXN];//重儿子
int pos;
void init()
{
tot = 0;
memset(head,-1,sizeof(head));
pos = 1;//使用树状数组,编号从头1开始
memset(son,-1,sizeof(son));
}
void addedge(int u,int v)
{
edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++;
}
void dfs1(int u,int pre,int d){
dep[u] = d;faz[u] = pre;num[u] = 1;
for(int i = head[u];i != -1; i = edge[i].next){
int v = edge[i].to;
if(v != pre){
dfs1(v,u,d+1);
num[u] += num[v];
if(son[u] == -1 || num[v] > num[son[u]])
son[u] = v;
}
}
}
void getpos(int u,int sp){
top[u] = sp;pid[u] = pos++;fpid[pid[u]] = u;
if(son[u] == -1) return;
getpos(son[u],sp);
for(int i = head[u];i != -1;i = edge[i].next){
int v = edge[i].to;
if( v != son[u] && v != faz[u])
getpos(v,v);
}
}
//树状数组
int c[MAXN];
int lowbit(int x){return x&-x;}
int sum(int x){
int ret=0;
while(x>0){
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
void add(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
//区间查询把add改成区间查询函数即可
void updatePath(int u,int v,int val){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]])swap(u,v);
add(pid[top[v]],val);
add(pid[v]+1,-val);
v=faz[top[v]];
}
if(dep[u]>dep[v])swap(u,v);
add(pid[u],val);
add(pid[v]+1,-val);
}
/********
*修改边权的代码中,边权存放在,dep较大的那个节点中
*寻找完lca后,if(u==v)return ans;否则将u改成son[u]
*即update(pid[son[u]],pid[v]);
*************/
int a[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
while(~scanf("%d%d%d",&n,&M,&P)){
init();
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
while(M--){
scanf("%d%d",&l,&r);
addedge(l,r);
addedge(r,l);
}
dfs1(1,0,0);
getpos(1,1);
memset(c,0,sizeof c);
for(int i=1;i<=n;i++){
add(pid[i],a[i]);
add(pid[i]+1,-a[i]);
}
//printf("ok\n");
while(P--){
scanf("%s",op);
if(op[0]=='Q'){
scanf("%d",&v);
printf("%d\n",sum(pid[v]));
}
else{
scanf("%d%d%d",&l,&r,&v);
v=v*(op[0]=='I'?1:-1);
updatePath(l,r,v);
}
}
}
return 0;
}
tarjan+并查集,离线
hdu 2586
tarjan+lca,适用于离线查询,板子题。
使用dfs和tarjan前要memset(vis,0,sizeof(vis))
修改N,复杂度O(n+q)
*/
#include
#define mp make_pair
using namespace std;
typedef pair<int,int> pii;
struct UFSet{
static const int N=4e4+10;
int fa[N];
void init(){for(int i=1;i<N;i++)fa[i]=i;}
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
};
struct TarjanLca{
static const int N=4e4+10;
vector<pii> G[N];
//vector G[N];
vector<pii> Q[N];
int ans[N];
bool vis[N],vvis[N];
int dep[N];
UFSet ufset;
void init(){
for(int i=0;i<N;i++)
G[i].clear(),Q[i].clear();
ufset.init();
memset(ans,-1,sizeof ans);
memset(vvis,0,sizeof vvis);
}
void Tarjan(int u){
int sz=G[u].size();
vis[u]=1;
for(int i=0;i<sz;i++){
int v=G[u][i].first;
if(!vis[v])
{
Tarjan(v);
int f1=ufset.Find(u);
int f2=ufset.Find(v);
//合并到父节点上
ufset.fa[f2]=f1;
}
}
vvis[u]=1;
sz=Q[u].size();
for(int i=0;i<sz;i++){
int v=Q[u][i].first;int index=Q[u][i].second;
if(vvis[v]){
ans[index]=dep[u]+dep[v]-2*dep[ufset.Find(v)];
}
}
}
void dfs(int u){
vis[u]=1;
int sz=G[u].size();
for(int i=0;i<sz;i++){
int v=G[u][i].first;
if(!vis[v]){//修改dep
dep[v]=dep[u]+G[u][i].second;
dfs(v);
}
}
}
};
TarjanLca tjlca;
int main(){
//freopen("in.txt","r",stdin);
int n,q;
int T;cin>>T;
while(T--){
cin>>n>>q;
tjlca.init();
int x,y,c;
for(int i=1;i<n;i++){
cin>>x>>y>>c;
tjlca.G[x].push_back(mp(y,c));
tjlca.G[y].push_back(mp(x,c));
}
for(int i=1;i<=q;i++){
cin>>x>>y;
tjlca.Q[x].push_back(mp(y,i));
tjlca.Q[y].push_back(mp(x,i));
}
memset(tjlca.vis,0,sizeof tjlca.vis);//使用前记得memset;
tjlca.dep[1]=0;
tjlca.dfs(1);
memset(tjlca.vis,0,sizeof tjlca.vis);//使用前记得memset;
tjlca.Tarjan(1);
for(int i=1;i<=q;i++)
cout<<tjlca.ans[i]<<endl;
}
return 0;
}
倍增算法
#include
using namespace std;
const int N=10000+5;
const int logN=20;
vector <int> son[N];
int T,n,depth[N],fa[N][logN],in[N],a,b;
void dfs(int prev,int u){
depth[u]=depth[prev]+1;
fa[u][0]=prev;
for (int i=1;i<logN;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for (int i=0;i<son[u].size();i++)
dfs(rt,son[u][i]);
}
int LCA(int x,int y){
if (depth[x]<depth[y])
swap(x,y);
for (int i=logN-1;i>=0;i--)
if (depth[x]-depth[y]>=(1<<i))
x=fa[x][i];
if (x==y)
return x;
for (int i=logN-1;i>=0;i--)
if (fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d",&n);
for (int i=1;i<=n;i++)
son[i].clear();
memset(in,0,sizeof in);
for (int i=1;i<n;i++){
scanf("%d%d",&a,&b);
son[a].push_back(b);
in[b]++;
}
depth[0]=-1;
int rt=0;
for (int i=1;i<=n&&rt==0;i++)
if (in[i]==0)
rt=i;
dfs(0,rt);
scanf("%d%d",&a,&b);
printf("%d\n",LCA(a,b));
}
return 0;
}
#include
#include
#define R register int
#define I inline void
#define lc c[x][0]
#define rc c[x][1]
#define G ch=getchar()
#define in(z) G;\
while(ch<'-')G;\
z=ch&15;G;\
while(ch>'-')z*=10,z+=ch&15,G;
const int N=300009;
int f[N],c[N][2],v[N],s[N],st[N];
bool r[N];
inline bool nroot(R x){//判断节点是否为一个Splay的根(与普通Splay的区别1)
return c[f[x]][0]==x||c[f[x]][1]==x;
}//原理很简单,如果连的是轻边,他的父亲的儿子里没有它
I pushup(R x){//上传信息
s[x]=s[lc]^s[rc]^v[x];
}
I pushr(R x){R t=lc;lc=rc;rc=t;r[x]^=1;}//翻转操作
I pushdown(R x){//判断并释放懒标记
if(r[x]){
if(lc)pushr(lc);
if(rc)pushr(rc);
r[x]=0;
}
}
I rotate(R x){//一次旋转
R y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
if(nroot(y))c[z][c[z][1]==y]=x;c[x][!k]=y;c[y][k]=w;//额外注意if(nroot(y))语句,此处不判断会引起致命错误(与普通Splay的区别2)
if(w)f[w]=y;f[y]=x;f[x]=z;
pushup(y);
}
I splay(R x){//只传了一个参数,因为所有操作的目标都是该Splay的根(与普通Splay的区别3)
R y=x,z=0;
st[++z]=y;//st为栈,暂存当前点到根的整条路径,pushdown时一定要从上往下放标记(与普通Splay的区别4)
while(nroot(y))st[++z]=y=f[y];
while(z)pushdown(st[z--]);
/*当然了,其实利用函数堆栈也很方便,代替上面几行手动栈,就像这样
I pushall(R x){
if(nroot(x))pushall(f[x]);
pushdown(x);
}*/
while(nroot(x)){
y=f[x];z=f[y];
if(nroot(y))
rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
rotate(x);
}
pushup(x);
}
I access(R x){//访问
for(R y=0;x;x=f[y=x])
splay(x),rc=y,pushup(x);
}
I makeroot(R x){//换根
access(x);splay(x);
pushr(x);
}
inline int findroot(R x){//找根(在真实的树中的)
access(x);splay(x);
while(lc)pushdown(x),x=lc;
//splay(x);(在本模板中建议不写)
return x;
}
I split(R x,R y){//提取路径
makeroot(x);
access(y);splay(y);
}
I link(R x,R y){//连边
makeroot(x);
if(findroot(y)!=x)f[x]=y;
}
I cut(R x,R y){//断边
makeroot(x);
if(findroot(y)==x&&f[x]==y&&!rc)){
f[x]=c[y][0]=0;
pushup(y);
}
}
int main()
{
register char ch;
R n,m,i,type,x,y;
in(n);in(m);
for(i=1;i<=n;++i){in(v[i]);}
while(m--){
in(type);in(x);in(y);
switch(type){
case 0:split(x,y);printf("%d\n",s[y]);break;
case 1:link(x,y);break;
case 2:cut(x,y);break;
case 3:splay(x);v[x]=y;//先把x转上去再改,不然会影响Splay信息的正确性
}
}
return 0;
}