倍增
很久之前就学习过的算法,但是最近发现很多题目都可以用倍增的方法解决,并且十分好写,所以总结一下。
noip2013 货车运输
题目大意:n各点构成一个无向图,每条边有一个最大负载,有q辆货车,分别从不同的点到不同的点,求每辆货车最大运载量。
思路:为了满足最大负载,肯定是走图的最大生成树上的路径,并且答案就是每次路径上的最小边权。用倍增维护最小边权就可以了。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; struct use{ int xx,yy,va; }r[50001]; int next[100001]={0},point[10001]={0},en[100001]={0},fa[10001]={0},f[10001][15]={0}, g[10001][15]={0},dep[10001]={0},val[100001]={0}; int my_comp(const use &x,const use &y) { if (x.va>y.va) return 1; else return 0; } int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } void dfs(int x,int i) { int j,y; dep[x]=i; for (j=1;j<=13;++j) { f[x][j]=f[f[x][j-1]][j-1]; g[x][j]=min(g[f[x][j-1]][j-1],g[x][j-1]); } y=point[x]; while (y!=0) { if (dep[en[y]]==0) { f[en[y]][0]=x; g[en[y]][0]=val[y]; dfs(en[y],i+1); } y=next[y]; } } int work(int x,int y) { int i,j,t,ans; if (dep[x]>dep[y]) { t=x;x=y;y=t; } ans=2100000000; for (i=13;i>=0;--i) { if (dep[x]<=dep[f[y][i]]) { ans=min(ans,g[y][i]); y=f[y][i]; } } if (x==y) return ans; for (i=13;i>=0;--i) if (f[x][i]!=f[y][i]) { ans=min(ans,min(g[x][i],g[y][i])); x=f[x][i];y=f[y][i]; } ans=min(ans,min(g[x][0],g[y][0])); return ans; } int main() { int r1,r2,n,m,q,i,j,x,y,tot=0; cin>>n>>m; for (i=1;i<=m;++i) scanf("%d%d%d",&r[i].xx,&r[i].yy,&r[i].va); sort(r+1,r+m+1,my_comp); for (i=1;i<=n;++i) { fa[i]=i; } for (i=1;i<=m;++i) { r1=rool(r[i].xx); r2=rool(r[i].yy); if (r1!=r2) { ++tot; next[tot]=point[r[i].xx]; point[r[i].xx]=tot; en[tot]=r[i].yy; val[tot]=r[i].va; ++tot; next[tot]=point[r[i].yy]; point[r[i].yy]=tot; en[tot]=r[i].xx; val[tot]=r[i].va; fa[r1]=r2; } } for (i=1;i<=n;++i) if (dep[i]==0) dfs(i,1); cin>>q; for (i=1;i<=q;++i) { scanf("%d%d",&x,&y); if (rool(x)!=rool(y)) printf("%d\n",-1); else printf("%d\n",work(x,y)); } }
水果姐逛水果街
题目大意:树上的一条路径,在使后面的权值减去前面的差最大。(变形1:可以修改权值;变形2:可以增加结点。)
思路:对于这种题目倍增或者链剖都可以解决。但是要十分注意信息(最大值、最小值、正着的最大差和反着的最大差)更新的顺序。(变形1:只能用链剖,可以很快的修改和查询;变形2:只能用倍增,因为倍增中,新加入一个点只需要用父亲的信息修改这个点的信息就可以了,不需要改动父亲,所以可以很快的求出。)
可是如果这两种变式结合在一起,该用什么呢?
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define maxnode 200005 #define inf 2100000000LL using namespace std; int fa[maxnode][20]={0},maxn[maxnode][20]={0},minn[maxnode][20]={0},zcha[maxnode][20]={0},tot=0, fcha[maxnode][20]={0},ai[maxnode]={0},point[maxnode]={0},next[maxnode*2]={0},en[maxnode*2]={0}, dep[maxnode]={0}; struct use{ int maxn,minn; }; void add(int u,int v) { ++tot;next[tot]=point[u];point[u]=tot;en[tot]=v; ++tot;next[tot]=point[v];point[v]=tot;en[tot]=u; } void ins(int u,int faa) { int i; fa[u][0]=faa;dep[u]=dep[faa]+1;maxn[u][0]=max(ai[u],ai[faa]);minn[u][0]=min(ai[u],ai[faa]); zcha[u][0]=max(0,ai[faa]-ai[u]);fcha[u][0]=max(0,ai[u]-ai[faa]); for (i=1;i<=18;++i) { fa[u][i]=fa[fa[u][i-1]][i-1]; zcha[u][i]=max(zcha[u][i-1],max(zcha[fa[u][i-1]][i-1],maxn[fa[u][i-1]][i-1]-minn[u][i-1])); fcha[u][i]=max(fcha[u][i-1],max(fcha[fa[u][i-1]][i-1],maxn[u][i-1]-minn[fa[u][i-1]][i-1])); maxn[u][i]=max(maxn[u][i-1],maxn[fa[u][i-1]][i-1]); minn[u][i]=min(minn[u][i-1],minn[fa[u][i-1]][i-1]); } } void dfs(int u,int faa) { int i,j; ins(u,faa); for (i=point[u];i;i=next[i]) if ((j=en[i])!=faa) dfs(j,u); } int ask(int u,int v) { int i,j,ans=0; use ans1,ans2; ans1.maxn=ans1.minn=ai[u];ans2.maxn=ans2.minn=ai[v]; if (dep[u]>dep[v]) { for (i=18;i>=0;--i) if (dep[fa[u][i]]>=dep[v]) { ans=max(ans,max(zcha[u][i],maxn[u][i]-ans1.minn)); ans1.maxn=max(ans1.maxn,maxn[u][i]); ans1.minn=min(ans1.minn,minn[u][i]); u=fa[u][i]; } } else { for (i=18;i>=0;--i) if (dep[fa[v][i]]>=dep[u]) { ans=max(ans,max(fcha[v][i],ans2.maxn-minn[v][i])); ans2.maxn=max(ans2.maxn,maxn[v][i]); ans2.minn=min(ans2.minn,minn[v][i]); v=fa[v][i]; } } if (u==v) return ans; for (i=18;i>=0;--i) { if (fa[u][i]!=fa[v][i]) { ans=max(ans,max(zcha[u][i],max(maxn[u][i]-ans1.minn, max(fcha[v][i],ans2.maxn-minn[v][i])))); ans1.maxn=max(ans1.maxn,maxn[u][i]); ans1.minn=min(ans1.minn,minn[u][i]); ans2.maxn=max(ans2.maxn,maxn[v][i]); ans2.minn=min(ans2.minn,minn[v][i]); u=fa[u][i];v=fa[v][i]; } } ans=max(ans,max(zcha[u][0],maxn[u][0]-ans1.minn)); ans1.maxn=max(ans1.maxn,maxn[u][0]); ans1.minn=min(ans1.minn,minn[u][0]); ans=max(ans,max(fcha[v][0],ans2.maxn-ans1.minn)); return ans; } int main() { freopen("lct.in","r",stdin); freopen("lct.out","w",stdout); int n,m,i,j,q,ans=0,kk,u,v; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&ai[i]); for (i=1;i<n;++i) { scanf("%d%d",&u,&v);add(u,v); } dfs(1,0);scanf("%d",&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&kk,&u,&v); if (kk==1) { u^=ans;v^=ans;ans=ask(u,v);printf("%d\n",ans); } else { v^=ans;ai[++n]=u;ins(n,v); } } fclose(stdin); fclose(stdout); }
bzoj2815 灾难
题目大意:给定不同生物的食用关系(保证无环),求每种生物的灾难值(即它灭绝之后不能存活的生物的个数)。
思路:听他们说这叫灾难树?其实就是考虑这张有向图中某个点所能到的所有点最后汇集到的那个点及那个点的灾难就是它的灾难。所以我们可以从被吃的向吃的拓扑,然后对于拓扑到的点用反向边的终点找到lca就是这个点到的所有点最后汇集的那个灾难,把这个点作为它灾难的儿子,那么一个点的子树大小-1就是这个点的灾难值了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100000 #define maxe 1000000 #define up 21 using namespace std; int tot=0,n,point[maxm]={0},en[maxe]={0},next[maxe]={0},du[maxm]={0},siz[maxm]={0}, que[maxm],fa[maxm][up]={0},point1[maxm]={0},en1[maxe]={0},next1[maxe]={0}, point2[maxm]={0},next2[maxe]={0},en2[maxe]={0},dep[maxm]={0}; void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v;++du[v]; next1[tot]=point1[v];point1[v]=tot;en1[tot]=u; } void add2(int u,int v){next2[++tot]=point2[u];point2[u]=tot;en2[tot]=v;} int lca(int a,int b){ int i,j;if (dep[a]<dep[b]) swap(a,b); for (i=20;i>=0;--i) if (dep[fa[a][i]]>=dep[b]) a=fa[a][i]; if (a==b) return a; for (i=20;i>=0;--i) if (fa[a][i]!=fa[b][i]){ a=fa[a][i];b=fa[b][i]; }return fa[a][0]; } void dfs(int u,int ff){ int i,v;siz[u]=1; for (i=point2[u];i;i=next2[i]) if ((v=en2[i])!=ff){ dfs(v,u);siz[u]+=siz[v]; } } void work(){ int i,j,u,v,head=0,tail=0,ll;que[++tail]=n+1;dep[n+1]=1; while(head!=tail){ u=que[++head]; for (i=point[u];i;i=next[i]){ --du[v=en[i]]; if (!du[v]){ que[++tail]=v; for (j=point1[v],ll=en1[j];j;j=next1[j])ll=lca(ll,en1[j]); add2(ll,v);fa[v][0]=ll;dep[v]=dep[ll]+1; for (j=1;j<=20;++j) fa[v][j]=fa[fa[v][j-1]][j-1]; } } }dfs(n+1,0); } int main(){ int i,j,u;scanf("%d",&n); for(i=1;i<=n;++i){ j=0; while(scanf("%d",&u)==1){ if (!u) break;add(u,i);++j; }if (!j) add(n+1,i); }tot=0;work(); for (i=1;i<=n;++i) printf("%d\n",siz[i]-1); }
noip2012 开车旅行
题目大意:给定n个城市排成一排,只能从1~n的方向走,定义两个城市的距离是高度差。两个人开一辆车去旅行,A先开选次近的点,B选最近的点,两人交替开车,这里距离的定义是:如果距离相等,选高度小的更近。求:1)从任意城市出发,给定x(走的最远的距离),A开的路程/B开的路程最小是多少,B的路程为0是认为是无穷大,且无穷大认为相等,如果有多个城市满足条件,输出海拔最高的;2)m组询问,每次询问给定s(起点)和x(走的最远的距离),尽量远走求A和B走的距离分别是多少。
思路:如果把A和B各开一次看作一次操作的话,可以预处理出从每个节点做一次操作的距离(A走多少、B走多少),然后倍增一下。然后我们在处理的时候就可以在倍增的数组上走了,最后判断一下A能不能再走一次就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100005 #define up 20 #define LL long long #define inf 2100000000 #define linf 0x7fffffffffffffffLL #define eps 1e-9 using namespace std; struct use{ int ll,rr; }tree[maxm*4]={0}; struct uu{LL da,db;}; int fa[maxm][up+1]={0},est[maxm]={0},er[maxm]={0},n,siz; LL fi[maxm][up+1]={0},gi[maxm][up+1]={0},ai[maxm]={0},bi[maxm]={0},xi; use updata(use x,use y){ use c; c.ll=(x.ll ? x.ll : y.ll); c.rr=(y.rr ? y.rr : x.rr); return c; } LL ab(LL x){return x<0 ? -x : x;} int cmp(double x,double y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0; } void ins(int i,int l,int r,int x,int y){ if (l==r){tree[i].ll=tree[i].rr=y;return;} int mid=(l+r)>>1; if (x<=mid) ins(i<<1,l,mid,x,y); else ins(i<<1|1,mid+1,r,x,y); tree[i]=updata(tree[i<<1],tree[i<<1|1]); } int pree(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return tree[i].rr; int mid=(l+r)>>1;int ans1=0,ans2=0; if (ll<=mid) ans1=pree(i<<1,l,mid,ll,rr); if (rr>mid) ans2=pree(i<<1|1,mid+1,r,ll,rr); return (ans2 ? ans2 : ans1); } int succ(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return tree[i].ll; int mid=(l+r)>>1;int ans1=0,ans2=0; if (ll<=mid) ans1=succ(i<<1,l,mid,ll,rr); if (rr>mid) ans2=succ(i<<1|1,mid+1,r,ll,rr); return (ans1 ? ans1 : ans2); } void pre(){ int i,j,nest,ner,pnest,pner;LL c1,c2; for (i=n;i;--i){ nest=pnest=ner=pner=0;c1=c2=linf; if (ai[i]>1){ nest=pree(1,1,siz,1,ai[i]-1); if (ai[nest]>1) pnest=pree(1,1,siz,1,ai[nest]-1); }if (ai[i]<siz){ ner=succ(1,1,siz,ai[i]+1,siz); if (ai[ner]<siz&&ner) pner=succ(1,1,siz,ai[ner]+1,siz); }if (nest&&bi[ai[i]]-bi[ai[nest]]<c1){ c1=bi[ai[i]]-bi[ai[nest]];est[i]=nest; }if (ner&&bi[ai[ner]]-bi[ai[i]]<c1) est[i]=ner; if (nest&&est[i]!=nest&&bi[ai[i]]-bi[ai[nest]]<c2){ c2=bi[ai[i]]-bi[ai[nest]];er[i]=nest; }if (pnest&&bi[ai[i]]-bi[ai[pnest]]<c2){ c2=bi[ai[i]]-bi[ai[pnest]];er[i]=pnest; }if (ner&&est[i]!=ner&&bi[ai[ner]]-bi[ai[i]]<c2){ c2=bi[ai[ner]]-bi[ai[i]];er[i]=ner; }if (pner&&bi[ai[pner]]-bi[ai[i]]<c2){ c2=bi[ai[pner]]-bi[ai[i]];er[i]=pner; }ins(1,1,siz,ai[i],i);fa[i][0]=est[er[i]]; if (er[i]) fi[i][0]=ab(bi[ai[er[i]]]-bi[ai[i]]); else fi[i][0]=inf; if (est[er[i]]) gi[i][0]=ab(bi[ai[est[er[i]]]]-bi[ai[er[i]]]); else gi[i][0]=inf; for (j=1;j<=up;++j){ fa[i][j]=fa[fa[i][j-1]][j-1]; fi[i][j]=fi[fa[i][j-1]][j-1]+fi[i][j-1]; gi[i][j]=gi[fa[i][j-1]][j-1]+gi[i][j-1]; } } } uu get(int x){ int i,j;LL ans=0;uu ci;ci.da=ci.db=0LL; for (i=up;i>=0;--i) if (ans+fi[x][i]+gi[x][i]<=xi){ ans+=fi[x][i]+gi[x][i]; ci.da+=fi[x][i];ci.db+=gi[x][i]; x=fa[x][i]; } if (ans+fi[x][0]<=xi){ ans+=fi[x][0];ci.da+=fi[x][0]; }return ci; } int main(){ int m,i,j,si;uu ci; double ans=inf*1.0,cc;scanf("%d",&n); for (i=1;i<=n;++i){scanf("%I64d",&ai[i]);bi[i]=ai[i];} sort(bi+1,bi+n+1);siz=unique(bi+1,bi+n+1)-bi-1; for (i=1;i<=n;++i) ai[i]=upper_bound(bi+1,bi+n+1,ai[i])-bi-1; pre();scanf("%I64d%d",&xi,&m); for (si=0,i=1;i<=n;++i){ ci=get(i); if (ci.db==0) cc=inf*1.0; else cc=(double)ci.da*1.0/(double)ci.db; if (cmp(cc,ans)==0&&ai[i]>ai[si]) si=i; if (cmp(cc,ans)<0){ans=cc;si=i;} }printf("%d\n",si); for (i=1;i<=m;++i){ scanf("%d%I64d",&si,&xi); ci=get(si);printf("%I64d %I64d\n",ci.da,ci.db); } }
bzoj2306 幸福路径(!!!)
题目大意:给定一张有向图,每次经过一个点,体力会变为之前的q倍,一条路径的价值是所有点的价值*当时的体力之和,求从某点出发的最大价值。保留1位小数。
思路:0<q<=1-1e-6,所以q在1e9次方左右就会很小了,所以用倍增+floyed求最长路,但是数组的含义应该是小于等于2^j步时的最大值(因为可能走不到2^j步这么多)。这里有一种给double清负无穷的方法,memset(,-20,)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 105 #define inf 2100000000.0 #define up 30 using namespace std; double fi[up+1][maxm][maxm],wi[maxm]; int main(){ int i,j,n,m,st,a,b,c,u,v;double p,ans=0.0; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) scanf("%lf",&wi[i]); scanf("%d%lf",&st,&p); memset(fi,-20,sizeof(fi)); for (i=1;i<=m;++i){scanf("%d%d",&u,&v);fi[0][u][v]=wi[v]*p;} for (i=1;i<=up;++i){ for (a=1;a<=n;++a) for (b=1;b<=n;++b) for (c=1;c<=n;++c) fi[i][b][c]=max(fi[i-1][b][c],max(fi[i][b][c],fi[i-1][b][a]+fi[i-1][a][c]*p)); p*=p; }for (i=1;i<=n;++i) ans=max(ans,fi[up][st][i]); printf("%.1f\n",ans+wi[st]); }
bzoj4444 国旗计划
题目大意:给定一个M长的环,每个人覆盖一个区间,问某个人必选的时候,要覆盖整个环的最小人数(保证一定可以覆盖且线段互不包含)。
思路:离散之后把环拆成两~三倍长,类似bzoj1178,用倍增预处理出每条线段向后2^i条线段是哪条(这里的向后是指有交集且交集最小,这样就可以使右端点尽量向后了),然后对每条线段所对应的区间用倍增求出不到右端点的线段数+2就是答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 400005 #define up 20 #define inf 2100000000 using namespace std; struct use{ int l,r,po; bool operator<(const use&x)const{return r<x.r;} }ai[N],ci[N]; int gi[N][up],bi[N],bz=0,cz=0,id[N]={0},n,m,mi[up]; int getf(int x){ int l,r,mid,ans;l=1;r=cz; while(l<=r){ int mid=(l+r)>>1; if (ci[mid].l<=x){ans=mid;l=mid+1;} else r=mid-1; }return ans;} int work(int x){ int i,j,u,ans=0; for (u=x,i=up-1;i>=0;--i) if (ci[gi[u][i]].r<ci[x].l+m){ u=gi[u][i]; ans+=mi[i]; } return ans+2;} int main(){ int i,j;scanf("%d%d",&n,&m); for (mi[0]=i=1;i<up;++i) mi[i]=mi[i-1]<<1; for (i=1;i<=n;++i){ scanf("%d%d",&ai[i].l,&ai[i].r); bi[++bz]=ai[i].l;bi[++bz]=ai[i].r; }sort(bi+1,bi+bz+1); m=bz=unique(bi+1,bi+bz+1)-bi-1; for (i=1;i<=n;++i){ ai[i].l=upper_bound(bi+1,bi+bz+1,ai[i].l)-bi-1; ai[i].r=upper_bound(bi+1,bi+bz+1,ai[i].r)-bi-1; if (ai[i].l<=ai[i].r){ ci[++cz]=ai[i]; ci[++cz]=(use){ai[i].l+m,ai[i].r+m}; }else{ ci[++cz]=(use){ai[i].l,ai[i].r+m}; ci[++cz]=(use){ai[i].l+m,ai[i].r+2*m}; }ci[cz].po=ci[cz-1].po=i; }sort(ci+1,ci+cz+1); for (i=1;i<=cz;++i) if (!id[ci[i].po]) id[ci[i].po]=i; ci[0]=(use){0,inf,0}; for (i=cz-1;i;--i){ gi[i][0]=getf(ci[i].r); for (j=1;j<up;++j) gi[i][j]=gi[gi[i][j-1]][j-1]; }for (i=1;i<=n;++i) printf("%d ",work(id[i])); }
bzoj4386 Wycieczki
题目大意:给一张无向图,边权是1、2、3,求第k小的路径(可以重复经过点或边)的长度。
思路:如果二分加矩乘判断是nlog^2n的,所以可以用倍增求<k的长度,然后+1就可以了。
注意:可能爆longlong,处理成k+1。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define LL long long #define N 125 #define M 65 using namespace std; LL k; int id[N][3],tot=0,n; struct mat{ LL nm[N][N]; void clear(){memset(nm,0,sizeof(nm));} void init(){clear();for (int i=1;i<=tot;++i) nm[i][i]=1LL;} mat operator*(const mat&x)const{ int i,j,kk;mat c; c.clear(); for (i=1;i<=tot;++i) for (kk=1;kk<=tot;++kk){ if (!nm[i][kk]) continue; for (j=1;j<=tot;++j){ if (x.nm[kk][j]>k/nm[i][kk]){ c.nm[i][j]=k+1LL; continue; }c.nm[i][j]+=nm[i][kk]*x.nm[kk][j]; if (c.nm[i][j]>k) c.nm[i][j]=k+1LL; } }return c; } }mi[N]; int main(){ int m,u,v,vv,i,j,kk;LL l=0LL,sm; mat a,b; scanf("%d%d%I64d",&n,&m,&k);k+=n; mi[0].clear(); for (i=1;i<=n;++i) for (j=0;j<3;++j){ id[i][j]=++tot; if (j) mi[0].nm[tot-1][tot]=1LL; } ++tot;mi[0].nm[tot][tot]=1LL; for (i=1;i<=n;++i) mi[0].nm[id[i][0]][tot]=1LL; for (i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&vv); ++mi[0].nm[id[u][vv-1]][id[v][0]]; }for (i=1;(1LL<<i)<=3LL*k;++i) mi[i]=mi[i-1]*mi[i-1]; a.init(); for (--i;i>=0;--i){ b=a*mi[i];sm=0LL; for (j=1;j<=n;++j){ for (kk=1;kk<=n;++kk){ sm+=b.nm[id[j][0]][id[kk][0]]; if (sm>k) sm=k+1LL; }sm+=b.nm[id[j][0]][tot]; if (sm>k) sm=k+1LL; }if (sm<k){a=b;l+=(1LL<<i);} }++l; if (l>3LL*k) printf("-1\n"); else printf("%I64d\n",l); }