参考:点击打开链接 点击打开链接 点击打开链接(一些总结) 点击打开链接(不错的模板)
题目:点击打开链接
花了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;
}