参考:点击打开链接 点击打开链接 点击打开链接(一些总结) 点击打开链接(不错的模板)
题目:点击打开链接
花了4天时间做完了这个专题,LCA的问题用处还是很大,同时能体会RMQ的ST算法中dp的味道.基本方法就是ST,LCA转RMQ,LCA的Tarjan,LCA倍增(这个可存储边权)
这个专题后面四道题都非常好,推荐大家做做.
细节:
1. ST方法2^i 包含自己,因此其真实只包含到i+2^k-1的范围.
2. Tarjan一般都很快,但不适合修改类型的问题,关于权值长度之类的,ST就不能用了.比如E
3. LCA转RMQ记得有三个数组,长度为2*N-1 ,很容易写错,然后会奇怪的报TLE错误.rmq里面存放的是下标.........
4.关于树遍历判重,不是par就行,可以不用vis,要add(a,b) add(b,a)不然会冲突顶死.E题就犯这个错误了.
模板:
void ST(int x){ int LOG=log(x+0.0)/log(2.0); for(int i=1;i<=x;i++) rmq[0][i]=i; for(int k=1;k<=LOG;k++) for(int i=1;i+(1<<k)-1<=x;i++){/// -1这里要注意 int a=rmq[k-1][i],b=rmq[k-1][i+(1<<k-1)]; if(dep[a]<dep[b]) rmq[k][i]=a; else rmq[k][i]=b; } } int RMQ(int a,int b){ if(a>b) swap(a,b); int LOG=log(b-a+1.)/log(2.0); int c=rmq[LOG][a],d=rmq[LOG][b-(1<<LOG)+1];///+1这里要注意 if(dep[c]<dep[d]) return c; else return d; }
void Tarjan(int u){ vis[u]=1; fa[u]=u; for(int i=head[u];~i;i=edge[i].nxt) if(!vis[edge[i].v]){ int v=edge[i].v; whead[v]=whead[u]+edge[i].w; Tarjan(v); fa[v]=u; } for(int i=qhead[u];~i;i=query[i].nxt) if(vis[query[i].v]){ int v=query[i].v; query[i].w=query[i^1].w=whead[u]+whead[v]-2*whead[Find(v)];///这里找祖先 } }
A - A Magic Lamp
从里面选几个数字使其最小,裸的RMQ HDU3183
#include<iostream> #include<cstdio> #include<cmath> #include<string> #include<string.h> using namespace std; int dp[20][1024]; char num[1024],hash[1024]; int min( int i,int j ) { return num[i]<=num[j]?i:j; } void RMQ( int n ) { for( int i=0; i< n ;i++ ) dp[0][i]=i; int t=( int )( log( double( n ) )/log( 2.0 ) ); for( int j=1; j<=t ; j++ ) for( int i=0; i+( 1<<j )-1<n; i++ ) dp[j][i]=min( dp[j-1][i],dp[j-1][i+( 1<<( j-1 ) )] ); } int result( int left,int right ) { int k=( int )( log( double( right-left+1 ) )/log( 2.0 ) ); return min( dp[k][left],dp[k][right-( 1<<k )+1] ); } int main( ) { int n; while( scanf( "%s%d",num,&n )!=EOF ){ int len=strlen( num ); RMQ( len ); int N=len-n; int i=0,j=0; while( N-- ){ i=result( i,len-N-1 ); hash[j++]=num[i++]; } for( i=0;i<j;i++ ) if( hash[i]!='0' ) break; if( i==j ) {printf( "0" );continue;} while( i<j ){ printf( "%c" , hash[i] ); i++; } puts( "" ); } return 0; }
B - Interviewe
HDU3486 直接枚举会超时,且并不符合二分的性质.自己慢慢优化吧.其实没什么意思.
C - Nearest Common Ancestors
POJ1330 LCA转RMQ问题,模板题
#include<ctime> #include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define eps 1e-8 #define long long ll; #define lowbit(x) (x&-x) const int MAXN = 2e4+10; int N,M,T; int nxt[MAXN],v[MAXN],head[MAXN],idx[2*MAXN],id,tot; int dep[MAXN],pos[MAXN],in[MAXN]; int rmq[MAXN][20]; int min( int i,int j ) { return dep[i]<=dep[j]?i:j; } void init(){ id=0; tot=0; memset(in,0,sizeof(in)); memset(head,-1,sizeof(head)); } void add(int a,int b){ v[tot]=b; nxt[tot]=head[a]; head[a]=tot++; } void dfs(int u,int d){ idx[id]=u; dep[id]=d; pos[u]=id++; for(int i=head[u];~i;i=nxt[i]){ dfs(v[i],d+1); idx[id]=u; dep[id++]=d; } } void RMQ(){ for(int i=0;i<id;i++) rmq[i][0]=i; int K=log(id)/log(2.0); for(int k=1;k<=K;k++) for(int i=0;i+(1<<k)-1<id;i++) rmq[i][k]=min(rmq[i][k-1],rmq[i+(1<<(k-1))][k-1]); } int Q(int a,int b){ if(a>b) swap(a,b); int K=log(b-a+1)/log(2.0); if(dep[min(rmq[a][K],rmq[b-(1<<K)+1][K])]==dep[rmq[a][K]]) return rmq[a][K]; return rmq[b-(1<<K)+1][K]; } int main(){ scanf("%d",&T); while(T--){ init(); scanf("%d",&N); for(int i=1;i<N;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b); in[b]++; } for(int i=1;i<=N;i++) if( !in[i]){ dfs(i,1); break; } RMQ(); int a,b;scanf("%d%d",&a,&b); printf("%d\n",idx[Q(pos[a],pos[b])]); } return 0; }
D - Closest Common Ancestors
POJ1470 同模板题,不过输入输出要注意一下,网上有好方法.
while(getchar()!='('); scanf("%d %d",&a,&b); while(getchar()!=')');
E - Distance Queries
POJ1986 两点间距离.都求出到根的距离,然后dist[u]+dist[v]-2*dist[lca]就可以了.
query[i].w=query[i^1].w=whead[u]+whead[v]-2*whead[Find(v)];///这里找祖先
F - How far away ?
和E差不多.HDU2586
G - Design the city
ZOJ3195 题意:给一个无根树,有q个询问,每个询问3个点,问将这3个点连起来,距离最短是多少,
LCA的模板题,分别求LCA(X,Y),LCA(X,Z),LCA(Y,Z),和对应的距离,然后3个距离相加再除以2就是这个询问的结果
H - Connections between cities
模板题太多HDU2874
I - Network
HDU3078 这个题按照题意做就行,不会超时.因为有修改点权值的操作,所以不能用倍增的方法存储最优解.
#include<ctime> #include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define eps 1e-8 #define long long ll; #define lowbit(x) (x&-x) const int MAXN = 100000+10; int N,M,K,T; struct P{ int to,nxt; }edge[2*MAXN]; int head[MAXN],tot; int num[MAXN],vis[MAXN]; int ans[MAXN]; int rmq[30][2*MAXN],dep[2*MAXN],pos[MAXN],seq[2*MAXN],id;/// int pre[MAXN]; bool cmp(int a, int b) { return a > b; } void init(){ tot=id=0; memset(vis,0,sizeof(vis)); memset(pre,-1,sizeof(pre)); memset(head,-1,sizeof(head)); } void add(int a,int b){ edge[tot].to=b; edge[tot].nxt=head[a]; head[a]=tot++; } void RMQ(){ int K=log(id+0.0)/log(2.0); for(int i=1;i<id;i++) rmq[0][i]=i; for(int i=1;i<=K;i++) for(int j=1;j+(1<<i)-1<id;j++) if(dep[ rmq[i-1][j] ]<dep[ rmq[i-1][j+(1<<(i-1))] ]) rmq[i][j]=rmq[i-1][j]; else rmq[i][j]=rmq[i-1][j+(1<<(i-1))]; } int Q(int a,int b){ if(a>b) swap(a,b); int K=log(b-a+1.0)/log(2.0); if(dep[ rmq[K][a] ]<dep[ rmq[K][b-(1<<K)+1] ])/// return rmq[K][a]; else return rmq[K][b-(1<<K)+1]; } void dfs(int u,int d){ vis[u]=1; seq[id]=u; dep[id]=d; pos[u]=id++; for(int i=head[u];~i;i=edge[i].nxt) if(!vis[edge[i].to]){ pre[edge[i].to]=u; dfs(edge[i].to,d+1); seq[id]=u; dep[id++]=d; } } int main(){ scanf("%d%d",&N,&M); init(); for(int i=1;i<=N;i++) scanf("%d",num+i); for(int i=1;i<N;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b); add(b,a); } dfs(1,1);RMQ(); for(int i=0;i<M;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); if(a==0) {num[b]=c;continue;} int d=seq[Q(pos[b],pos[c])]; int cnt=0; for(int i=b;i!=d;i=pre[i]) ans[cnt++]=num[i]; for(int i=c;i!=d;i=pre[i]) ans[cnt++]=num[i]; ans[cnt++]=num[d]; sort(ans,ans+cnt,cmp); if(cnt<a) puts("invalid request!"); else printf("%d\n",ans[a-1]); } return 0; }
J - Housewife Wind
poj2763 好题!用树状数组优化加速.这里对于欧拉数组,有个特点改变一个点或者该点父边.则受影响的孩子都在欧拉数组中间.first为第一次访问点,second为结束访问点.若有改变
就add(first[u],x),add(second[u]+1,-x) 求和就是sum(x)+sum(y)-sum(lca);
K - Network
poj3694 和AC程序对拍了很多数据都一样但是RE,懒得找问题了.
用Tarjan缩点
#include<ctime> #include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define eps 1e-8 #define long long ll; #define lowbit(x) (x&-x) const int MAXN = 400000+10; int N,M,S,K,T; struct P{ int st,to,w,nxt; }edge[2*MAXN]; int vis[MAXN],head[MAXN],tot,idx; int dep[MAXN],fa[MAXN],parent[MAXN]; int dfn[MAXN],low[MAXN],isbridge[MAXN],bcnt; int mystack[MAXN],top,instack[MAXN]; int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);} void init(){ tot=bcnt=idx=top=0; memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); memset(fa,-1,sizeof(fa)); memset(isbridge,0,sizeof(isbridge)); } void add(int a,int b){ edge[tot].st=a; edge[tot].to=b; edge[tot].nxt=head[a]; head[a]=tot++; } void Tarjan(int u,int d){ dfn[u]=low[u]=idx++; mystack[top++]=u; vis[u]=1; dep[u]=d; for(int i=head[u];~i;i=edge[i].nxt) { int v=edge[i].to; if(!vis[v]){ parent[v]=u;///回溯上去 Tarjan(v,d+1); low[u]=min(low[u],low[v]); if(dfn[u]<low[v]) bcnt++,isbridge[v]=1;///点的父边代表其 } else if(vis[v] == 1) low[u]=min(low[u],dfn[v]);/// double edge ignore it 若本身有反向边就不存在这个了 } if(dfn[u]==low[u]){ int k; do{ k=mystack[--top]; fa[k]=u; }while(top && k!=u); } vis[u]=2; } void LCA(int a,int b){ if(dfn[a]<dfn[b]) swap(a,b); int c=Find(a),d=Find(b); if(c==d && c!=-1) return; while(dep[a]>dep[b]){ if(isbridge[a]) bcnt--; isbridge[a]=0; a=parent[a]; } while(a!=b){ if(isbridge[a]) bcnt--; if(isbridge[b]) bcnt--; isbridge[a]=isbridge[b]=0; a=parent[a];b=parent[b]; } fa[c]=d; } int main(){ int q,cas=0; while(~scanf("%d%d",&N,&M)){ if(N==0 && M==0) break; init(); for(int i=0;i<M;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b); } Tarjan(1,1); printf("Case %d:\n",++cas); scanf("%d",&q); for(int i=0;i<q;i++){ int u,v; scanf("%d%d",&u,&v); LCA(u,v); printf("%d\n",bcnt); } puts(""); } return 0; }
L - Network
poj3417 好题,减掉一条原来就有的边,减掉一条增加的边,问有几种方法可以让图变成两部分.
想一下就知道若是树的话每条都是关键的,那么新边随便减 M
若被环覆盖一次,只要减去构成环的那条边,1种方法,
若>1则没用``````
思路是这个,但是实现很有技巧,dp[u]表示被环覆盖几次. 对一条新边(u,v),则有dp[u]++,dp[v]++ ,dp[lca]-=2
最后在dfs一次累加上去就OK.dp[u]+=dp[v]
#include<ctime> #include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define eps 1e-8 #define long long ll; #define lowbit(x) (x&-x) const int MAXN = 100000+10; int N,M,S,K,T; struct P{ int st,to,nxt; }edge[2*MAXN]; int head[MAXN],dep[MAXN<<1],idx[MAXN<<1],first[MAXN],id,tot; int vis[MAXN],dp[MAXN]; int rmq[30][MAXN<<1]; void init(){ tot=id=0; memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); } void ST(int x){ int LOG=log(x+0.0)/log(2.0); for(int i=1;i<=x;i++) rmq[0][i]=i; for(int k=1;k<=LOG;k++) for(int i=1;i+(1<<k)-1<=x;i++){/// -1 int a=rmq[k-1][i],b=rmq[k-1][i+(1<<k-1)]; if(dep[a]<dep[b]) rmq[k][i]=a; else rmq[k][i]=b; } } int RMQ(int a,int b){ if(a>b) swap(a,b); int LOG=log(b-a+1.)/log(2.0); int c=rmq[LOG][a],d=rmq[LOG][b-(1<<LOG)+1];///+1 if(dep[c]<dep[d]) return c; else return d; } void add(int a,int b){ edge[tot].st=a; edge[tot].to=b; edge[tot].nxt=head[a]; head[a]=tot++; } void dfs(int u,int d,int par){ dep[++id]=d; idx[id]=u;first[u]=id; for(int i=head[u];~i;i=edge[i].nxt) if(edge[i].to !=par){ dfs(edge[i].to,d+1,u); idx[++id]=u; dep[id]=d; } } int getans(int u,int par){ int res=0; for(int i=head[u];~i;i=edge[i].nxt) if(edge[i].to!=par){ res+=getans(edge[i].to,u); dp[u]+=dp[edge[i].to]; } if(u==1 || dp[u]>=2) return res; if(dp[u]==0) return res+M; if(dp[u]==1) return res+1; } int main(){ int q,cas=0; while(~scanf("%d%d",&N,&M)){ init(); for(int i=1;i<N;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b); add(b,a); } dfs(1,1,-1);ST(id); for(int i=0;i<M;i++){ int u,v; scanf("%d%d",&u,&v); int lca=idx[RMQ(first[u],first[v])]; dp[u]++;dp[v]++;dp[lca]-=2; } printf("%d\n",getans(1,-1)); } return 0; }
M - The merchant
poj 3728 题意大致是从两个点间倒手货物,使得获利最大,求价值.
这个题暗含了方向的意思,买卖次序不能颠倒,我刚开始一直用倍增方法找最小最大,那么久分两种u--->lca--->v 那么三种情况
1. 在u--->lca过程中买卖完成 2. lca--->v 买卖完成 3.u--->lca 买 lca--->v 卖
那么使用Tarjan方法每次维护四个数组
mx u到lca最大值 mn u到lca最小值 up 从u到lca最大获利, down 从lca到u最大获利.
那么一条路径(u,v) 的最大获利=max(up[u],down[u],mx[v],mn[u]); 最后面这个因为是低买高卖
如何维护就类似于关系并查集了.并且要逐层回答,因为这几个数组网上情况会变,因此要将同lca的询问一起回答.
关系并查集就是我 和父亲的关系 父亲和祖先的关系 推导出我和祖先的关系,对应此题就是求所维护的数组中数最优.
#include<ctime> #include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define eps 1e-8 #define long long ll; #define lowbit(x) (x&-x) const int MAXN = 100000+10; int N,M,S,K,T; struct P{ int st,to,nxt,id; }edge[MAXN<<3]; int head[MAXN],head2[MAXN],head3[MAXN],id,tot; int fa[MAXN],cost[MAXN],vis[MAXN]; int mx[MAXN],mn[MAXN],up[MAXN],down[MAXN]; int ans[MAXN]; int Find(int x){ if(x==fa[x]) return x; int t=fa[x]; fa[x]=Find(fa[x]); up[x]=max(up[t],max(up[x],mx[t]-mn[x]));/// down[x]=max(down[t],max(down[x],mx[x]-mn[t])); mx[x]=max(mx[x],mx[t]); mn[x]=min(mn[x],mn[t]); return fa[x]; } void init(){ tot=id=0; memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); memset(head2,-1,sizeof(head2)); memset(head3,-1,sizeof(head3)); } void add(int a,int b,int c){ edge[tot].id=c; edge[tot].st=a; edge[tot].to=b; edge[tot].nxt=head[a]; head[a]=tot++; } void add_ask(int a,int b,int c){ edge[tot].id=c; edge[tot].st=a; edge[tot].to=b; edge[tot].nxt=head2[a]; head2[a]=tot++; } void Tarjan(int u,int par){ fa[u]=u;vis[u]=1; for(int i=head[u];~i;i=edge[i].nxt) if(edge[i].to !=par){ Tarjan(edge[i].to,u); fa[edge[i].to]=u; } for(int i=head2[u];~i;i=edge[i].nxt) if(vis[edge[i].to]){ int t=Find(edge[i].to); edge[tot].id=i; edge[tot].nxt=head3[t]; head3[t]=tot++; }///回溯只有逐层LCA才有效,利用根的唯一性.之前就是找到就回溯,WA for(int i=head3[u];~i;i=edge[i].nxt){ int k=edge[i].id; int a=edge[k].st,b=edge[k].to,c=edge[k].id; if(ans[abs(c)]) continue; Find(a); if(c<0) swap(a,b),c=-c; ans[c]=max( up[a], max(down[b],mx[b]-mn[a]) ); } } /// int main(){ while(~scanf("%d",&N)){ init(); for(int i=1;i<=N;i++) scanf("%d",cost+i),mn[i]=mx[i]=cost[i],up[i]=down[i]=0; for(int i=1;i<N;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b,i); add(b,a,-i); } int q; scanf("%d",&q); for(int i=1;i<=q;i++){ int a,b; scanf("%d%d",&a,&b); add_ask(a,b,i); add_ask(b,a,-i); } Tarjan(1,-1); for(int i=1;i<=q;i++) printf("%d\n",ans[i]); } return 0; }
N - Checkers
HDU 3830 任何一个状态,通过题目所给的移动,都能对应且唯一对应一个b*2=a+c(a<b<c)的状态,这是突破点
其三个点合起来看做一个状态,至多三种转移状态.因此就很像二叉树,并且根的状态唯一,左右两边相等.所以以这个为根
如果不是根的状态,可以让左右两个往里跳,依据是让和中间那个坐标距离缩小
这样就可以看作为是向根移动 图并不用显示构造出来,用辗转相除的思想就可以解决,
然后二分+LCA.这里的二分注意,从点出发往根走,满足题意了才是减少步数,且不像一般的二分high=mid-1.因为这样很可能就过头了
#include<ctime> #include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define eps 1e-8 typedef long long ll; #define lowbit(x) (x&-x) const int MAXN = 100000+10; int N,M,S,K,T; typedef struct { ll a,b,c,dep; }state; state st,ed,srt,ert; int head[MAXN],head2[MAXN],head3[MAXN],id,tot; int fa[MAXN],cost[MAXN],vis[MAXN]; int ans[MAXN]; void mysort(state &x){ if(x.a>x.b) swap(x.a,x.b); if(x.a>x.c) swap(x.a,x.c); if(x.b>x.c) swap(x.b,x.c); } bool cmp(state a,state b){ if(a.a==b.a && a.b==b.b && a.c==b.c) return true; return false; } state findroot(state& x){ ll dep=0; ll a=x.a,b=x.b,c=x.c,t; while(b-a!=c-b){ ll len=b-a,_len=c-b; if(len>_len){ t=(len-1)/_len; b-=t*_len; c-=t*_len; } else{ t=(_len-1)/len; b+=t*len; a+=t*len; } dep+=t; } x.dep=dep; state res={a,b,c,0}; return res; } state update(state x,ll delta){ ll a=x.a,b=x.b,c=x.c,t; while(delta>0){ ll len=b-a,_len=c-b; if(len>_len){ t=(len-1)/_len; if(t>delta) t=delta; b-=t*_len; c-=t*_len; } else{ t=(_len-1)/len; if(t>delta) t=delta; b+=t*len; a+=t*len; } delta-=t; } state res={a,b,c}; return res; } int main(){ while(~scanf("%I64d%I64d%I64d",&st.a,&st.b,&st.c)){ scanf("%I64d%I64d%I64d",&ed.a,&ed.b,&ed.c); mysort(st);mysort(ed); srt=findroot(st); ert=findroot(ed); if(!cmp(srt,ert)){ puts("NO");continue; } if(st.dep<ed.dep) swap(st,ed); ll ans=st.dep-ed.dep; st=update(st,ans); ll l=0,h=ed.dep; state t1,t2; while(l<h){ ll mid=(l+h)>>1; t1=st,t2=ed; t1=update(st,mid); t2=update(ed,mid); if(cmp(t1,t2)) h=mid;///二分法起初多写了个-1,WA else l=mid+1; } printf("YES\n%I64d\n",ans+2*l); } return 0; }