标签:
数据结构
bzoj4546(可持久化Trie)
中文题题意我就不说了 解析: 可持久化Trie的模板题,详见注释 #include<cstdio> #include<cstring> #include<string> #include<algorithm> using namespace std; const int maxbit=19; const int maxn=10500005; int tr[500002]; struct PerTrie { int id; int next[maxn][2],num[maxn]; void init(){ id=next[0][0]=next[0][1]=num[0]=0; }//初始化 int f(int x,int i){ return (x>>i)&1; } //判断x的第i位为0或1 void Insert(int& rt,int pre,int x,int pos) //插入 { rt=++id; next[rt][0]=next[pre][0]; //赋等 next[rt][1]=next[pre][1]; num[rt]=num[pre]+1; //数量加1 if(pos==-1) return; int d=f(x,pos); Insert(next[rt][d],next[pre][d],x,pos-1); } int MaxXor(int l,int r,int x) { int ret=0; for(int i=maxbit;i>=0;i--) { int d=f(x,i); int a=next[l][d^1],b=next[r][d^1]; if(num[b]-num[a]>0) ret|=(1<<i),l=a,r=b; //判断是否存在 else l=next[l][d],r=next[r][d]; } return ret; } int MinNum(int l,int r,int x) { int ret=0; for(int i=maxbit;i>=0;i--) { int d=f(x,i); if(d) ret+=num[next[r][0]]-num[next[l][0]]; //比它小的加上 l=next[l][d]; r=next[r][d]; } ret+=num[r]-num[l]; //<=要加上它,<的话就不用了 return ret; } int Kth(int l,int r,int k) { int ret=0; for(int i=maxbit;i>=0;i--) { int t=num[next[r][0]]-num[next[l][0]]; if(t>=k) l=next[l][0],r=next[r][0]; //足够 else ret|=(1<<i),l=next[l][1],r=next[r][1],k-=t; } return ret; } }PT; int main() { int Q; scanf("%d",&Q); int type,l,r,x,cnt=0; tr[0]=0; PT.init(); while(Q--) { scanf("%d",&type); if(type==1) { scanf("%d",&x); ++cnt; PT.Insert(tr[cnt],tr[cnt-1],x,maxbit); //插入新的值 } else if(type==2) { scanf("%d%d%d",&l,&r,&x); printf("%d\n",PT.MaxXor(tr[l-1],tr[r],x)^x); } else if(type==3) { scanf("%d",&x); cnt-=x; } else if(type==4) { scanf("%d%d%d",&l,&r,&x); printf("%d\n",PT.MinNum(tr[l-1],tr[r],x)); } else { scanf("%d%d%d",&l,&r,&x); printf("%d\n",PT.Kth(tr[l-1],tr[r],x)); } } return 0; }
bzoj2741(分块+可持久化Trie)
题意中文我就不说了 解析: 分块+可持久化Trie,先得到前缀异或值,插入到Trie中,然后分块,对每一块,处理出dp[i][j](i代表第几块,j代表第几个位置),dp[i][j]代表以第i块开始的到j这个位置 的连续字串最大异或值。查询时,如果l,r不在同一块内,可以先查询l所在的块的后一个块到r的连续字串最大异或值,之前的dp就可以派上用场了,然后就是处理l到l所在块 的这段区间,取两者最大值即可。 代码 #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; const int maxn=12005; const int maxbit=31; int N,M,A[maxn]; int tr[maxn]; struct PerTrie { int next[10000005][2],num[10000005]; int id; void init(){ id=next[0][0]=next[0][1]=num[0]=0; } int f(int x,int i){ return (x>>i)&1; } void Insert(int& rt,int pre,int x,int pos) //插入 { rt=++id; next[rt][0]=next[pre][0]; next[rt][1]=next[pre][1]; num[rt]=num[pre]+1; if(pos==-1) return; int d=f(x,pos); Insert(next[rt][d],next[pre][d],x,pos-1); } int MaxXor(int l,int r,int x) //查询最大异或值,因为A[i]保存 { //的是前缀异或值,所以得到的结果就是某一段区间的异或值 int ret=0; for(int i=maxbit;i>=0;i--) { int d=f(x,i); int a=next[l][d^1],b=next[r][d^1]; if(num[b]-num[a]>0) ret|=(1<<i),l=a,r=b; else l=next[l][d],r=next[r][d]; } return ret; } }PT; int block,num,bel[maxn],dp[120][maxn]; //dp保存第几块到第几个数的区间最大异或值 void init() { tr[0]=0; PT.init(); for(int i=1;i<=N;i++) PT.Insert(tr[i],tr[i-1],A[i],maxbit); //插入 block=(int)sqrt(N+0.5); num=N/block; if(N%block) num++; //加1 memset(dp,0,sizeof(dp)); bel[0]=0; for(int i=1;i<=N;i++) bel[i]=(i-1)/block+1; //记录下属于哪个块 for(int i=1;i<=num;i++) { int st=(i-1)*block+1; for(int j=st;j<=N;j++) { dp[i][j]=max(dp[i][j-1],A[j]^A[st-1]); //可能是[st,j]这段区间 dp[i][j]=max(dp[i][j],PT.MaxXor(tr[st-1],tr[j],A[j])); //再找最大的 } } } int GetAns(int l,int r) { l--; int s=bel[l],ret=0; if(bel[r]>s) ret=dp[s+1][r]; //查询从后面一个块开始的 for(int i=l;i<=min(r,s*block);i++) { ret=max(ret,PT.MaxXor(tr[l-1],tr[r],A[i])); } return ret; } int main() { scanf("%d%d",&N,&M); A[0]=0; int x; for(int i=1;i<=N;i++) { scanf("%d",&x); A[i]=A[i-1]^x; } init(); int last=0,l,r; while(M--) { scanf("%d%d",&l,&r); l=(l+(LL)last)%N+1; r=(r+(LL)last)%N+1; if(l>r) swap(l,r); //printf("%d %d\n",l,r); last=GetAns(l,r); printf("%d\n",last); } return 0; }
poj2761(伸展树求名次)
题意:查询一段区间的第k大值,而且题目保证查询区间是不存在包含关系。 解析:因为不存在包含关系,所以直接离线排序,向右插入元素,向右删除元素。利用treap树实现名次树的功能。 #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; const int INF=1e9+7; const int maxn=100005; int A[maxn],cnt; //A数组保存数,cnt是节点标号,我是用数组模拟的 struct treap { treap* son[2]; //左右儿子 int v,s,r; treap(){ v=s=r=0; son[0]=son[1]=NULL; } treap(int nv,int nr); int rk(){ return son[0]->s+1; } //排名,第几个数 int cmp(int k) //比较,如果相等返回-1,小于返回0,大于1 { if(k==v) return -1; return k<v?0:1; } void pushup(){ s=son[0]->s+son[1]->s+1; } //更新大小 }null,tr[maxn]; treap::treap(int nv,int nr) { v=nv; r=nr; s=1; son[0]=son[1]=&null; } treap* NewNode(int x,int r)//建新节点 { tr[cnt]=treap(x,r); return tr+cnt++; } struct splaytree { int Size; treap* root; splaytree(){ Size=0; root=&null; } void Rotate(treap* &t,int d) //翻转操作 { treap* p=t->son[d^1]; t->son[d^1]=p->son[d]; p->son[d]=t; t->pushup(); //要更新 t=p; t->pushup(); } void Insert(treap* &t,int x,int r) //插入 { if(t==&null) //插入 { t=NewNode(x,r); //申请新节点 return; } int d=t->cmp(x); if(d==-1) d=1; //往右边走 Insert(t->son[d],x,r); if(t->son[d]->r > t->r) Rotate(t,d^1); //旋转 t->pushup(); } void Remove(treap* &t,int x) //删除 { int d=t->cmp(x); if(d==-1) { if(t->son[0]==&null) t=t->son[1]; else if(t->son[1]==&null) t=t->son[0]; else { int d2=(t->son[0]->r > t->son[1]->r ? 1: 0); Rotate(t,d2); Remove(t->son[d2],x); } } else Remove(t->son[d],x); if(t!=&null) t->pushup(); } int Query(treap* &t,int kth) //查询 { if(t==&null||kth<=0||kth>t->s) return -1; int a=t->rk(); if(kth==a) return t->v; else if(kth<a) return Query(t->son[0],kth); else return Query(t->son[1],kth-a); } }; int N,M; struct Ques { int x,y,k,id; Ques(int x=0,int y=0,int k=0,int id=0):x(x),y(y),k(k),id(id){} bool operator < (const Ques& t) const { if(x!=t.x) return x<t.x; return y<t.y; } }q[maxn]; int ans[maxn]; int main() { scanf("%d%d",&N,&M); splaytree spt; cnt=0; for(int i=1;i<=N;i++) scanf("%d",&A[i]); int x,y,k; for(int i=0;i<M;i++) //输入 { scanf("%d%d%d",&x,&y,&k); q[i]=Ques(x,y,k,i); } sort(q,q+M); //排序 int f=1,r=1; for(int i=0;i<M;i++) { Ques& t=q[i]; int x=t.x,y=t.y,k=t.k; for(;f<x;f++) if(f<r) spt.Remove(spt.root,A[f]); if(r<f) r=f; for(;r<=y;r++) spt.Insert(spt.root,A[r],rand()); ans[t.id]=spt.Query(spt.root,k); //保存答案 } for(int i=0;i<M;i++) printf("%d\n",ans[i]); return 0; }
hdu3487(伸展树分裂合并)
题意:起初整个排列是1,2,,,N,然后有两种操作 CUT a b c 将[a,b]分离出来后插入到剩余的数的第c个位置之后 FLIP a b 将[a,b]翻转 求最后的排列。 解析:伸展树合并分裂。对于CUT操作,先把左边和右边的分裂出来,再合并在一起,再分成[1,c]和[c+1,k](k是剩余的个数,注意区间可能为空,特殊处理一下就好了), 再将[a,b]插入进去。对于FLIP操作,还是分裂,再将[a,b]翻转。最后将所有翻转标记下压。记录答案即可。 #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; const int INF=1e9+7; const int maxn=300005; int N,M,ans,A[maxn],cnt; //A数组保存数,cnt是节点标号,我是用数组模拟的 struct treap { treap* son[2]; //左右儿子 int v,s,rev; treap(){ v=s=rev=0; son[0]=son[1]=NULL; } treap(int nv); int rk(){ return son[0]->s+1; } //排名,第几个数 int cmp(int k) //比较,如果相等返回-1,小于返回0,大于1 { if(k==rk()) return -1; return k<rk()?0:1; } void pushup(){ s=son[0]->s+son[1]->s+1; } //更新大小 void pushdown(); //处理懒惰标记 }null,tr[maxn]; treap::treap(int nv) { v=nv; s=1; rev=0; son[0]=son[1]=&null; } void treap::pushdown() { if(this==&null) return; if(rev) { swap(son[0],son[1]); son[0]->rev^=1; son[1]->rev^=1; rev=0; } } treap* NewNode(int x) { tr[cnt]=treap(x); return tr+cnt++; } struct splaytree { int Size; treap* root; splaytree(){ Size=0; root=&null; } void Rotate(treap* &t,int d) //翻转操作 { t->pushdown(); treap* p=t->son[d^1]; p->pushdown(); t->son[d^1]=p->son[d]; p->son[d]=t; t->pushup(); t=p; t->pushup(); } void Splay(treap* &t,int k) //将第k大的节点伸展到根 { t->pushdown(); int d=t->cmp(k); if(d!=-1) { if(d) Splay(t->son[d],k- t->rk()); else Splay(t->son[d],k); Rotate(t,d^1); } t->pushup(); } void Build(treap* &t,int le,int ri) //将N个数建成一棵树 { if(le>ri) return; int mid=(le+ri)/2; t=NewNode(mid); Build(t->son[0],le,mid-1); Build(t->son[1],mid+1,ri); t->pushup(); } void Cut(treap* &t,int a,int b,int c) { int len=b-a+1; if(len==N) return; //是整个区间就不用管了 Splay(t,a); t->pushdown(); //分裂出左边的 treap *L=t->son[0]; L->pushdown(); t->son[0]=&null; t->pushup(); Splay(t,len); t->pushdown(); //分裂出右边的 treap *R=t->son[1]; R->pushdown(); t->son[1]=&null; t->pushup(); treap *nt; if(R!=&null) //左右合并 { nt=R; Splay(nt,1); nt->son[0]=L; nt->pushup(); } else { nt=L; Splay(nt,a-1); nt->son[1]=R; nt->pushup(); } if(c+len==N) //在整个之后特殊处理一下就好 { Splay(nt,c); Splay(t,1); t->son[0]=nt; t->pushup(); return; } Splay(nt,c+1); treap *l=nt->son[0]; l->pushdown(); nt->son[0]=&null; nt->pushup(); t->son[1]=nt; t->pushup(); Splay(t,1); t->son[0]=l; t->pushup(); } void Reverse(treap* &t,int a,int b) //翻转 { Splay(t,a); //左边 treap *L=t->son[0]; L->pushdown(); t->son[0]=&null; t->pushup(); Splay(t,b-a+1); //右边 treap *R=t->son[1]; R->pushdown(); t->son[1]=&null; t->pushup(); t->rev^=1; //置翻转标记 t->pushdown(); t->son[0]=L; t->pushup(); Splay(t,b); t->son[1]=R; t->pushup(); } void PushAll(treap* &t) //中序遍历 { if(t==&null) return; t->pushdown(); PushAll(t->son[0]); A[++ans]=t->v; PushAll(t->son[1]); t->pushup(); } }; int main() { while(scanf("%d%d",&N,&M)!=EOF) { if(N<0&&M<0) break; splaytree spt; cnt=0; spt.Build(spt.root,1,N); //建树 int a,b,c; char op[10]; while(M--) { scanf("%s",op); if(op[0]==‘C‘) //CUT操作 { scanf("%d%d%d",&a,&b,&c); spt.Cut(spt.root,a,b,c); } else //FLIP操作 { scanf("%d%d",&a,&b); spt.Reverse(spt.root,a,b); } } ans=0; spt.PushAll(spt.root); //整个下压 for(int i=1;i<=ans;i++) printf("%d%c",A[i],i==ans?‘\n‘:‘ ‘); } return 0; }
hdu3436(伸展树)
题意:刚开始给出一个N,代表初始是1,2,3...N排成一行,有三种操作 Top x 将值x置于最前面 Query x 查询值x排在第几 Rank x 查询排在第x的位置是数值几 解析:这道题坑了我半天,一直超时,看了别人的博客,原来是自己 的Splay写low了,而且要加一个地方才能不超时,我代码中有注释。 数据达到10^8,显然不能建这么大一颗树,需要离散化,把Top和Query操作 的数保存下来离散化,然后处理出一段段区间 一开始按照区间段排在第几个的位置建树,要保存区间段v对应的是哪个节点的 编号,如果是Top操作,先二分找到x对应的区间k,将k对应的节点伸展到根,然后 删除这个节点,再插入最右边,如果是Query操作,也是二分找到x对应的区间k, 将k对应的节点伸展到根,即可得到他的排名。如果是Rank操作,从根开始找就行, 判断是否在某一段区间内。然后找到了就可以得到答案了。 #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; #define t tr[r] #define y tr[fa] const int maxn=200005; int N,Q,A[maxn];//A数组用于保存离散化的数 char op[maxn][7]; //操作字符串 int opx[maxn],seg[maxn][2],Size;//操作值,区间左右端点,多少个区间 int BIS(int v) //二分找区间下标 { int x=1,yy=Size,mid; while(x<=yy) { int mid=(x+yy)/2; if(seg[mid][0]<=v&&v<=seg[mid][1]) return mid; //找到了 if(seg[mid][1]<v) x=mid+1; else yy=mid; } } int bel[maxn]; //用于保存第几段区间现在的节点编号 struct treap { int son[2],fa; //左右儿子和父亲 int s,v,num; //s是大小,v代表区间的下标,num代表这段区间的大小 treap(){ s=v=num=0; son[0]=son[1]=fa=0; } int rk();//排名 void pushup(); //更新 int cmp(int k); //比较 }tr[maxn*2]; int treap::rk(){ return tr[son[0]].s+num; } void treap::pushup(){ s=tr[son[0]].s+tr[son[1]].s+num; } int treap::cmp(int k) { if(tr[son[0]].s<k&&k<=rk()) return -1;//在区间内 if(k<rk()) return 0; else return 1; } struct splaytree { int id,root; void init(){ id=root=0; tr[0].s=tr[0].num=0; }//初始化 void dfs(int r) { if(r==0) return; dfs(t.son[0]); printf("%d ",t.v); dfs(t.son[1]); } void Visit(){ dfs(root); puts("");} int NewNode(int fa,int v) //得到新节点 { bel[v]=++id; //保存v值对应的下标 int r=id; t.fa=fa; t.v=v; t.s=t.num=seg[v][1]-seg[v][0]+1; t.son[0]=t.son[1]=0; return r; } void Build(int &r,int le,int ri,int fa)//建树 { if(le>ri) return; //区间不存在 int mid=(le+ri)/2; r=NewNode(fa,mid); //得到新节点 Build(t.son[0],le,mid-1,r); //左建树 Build(t.son[1],mid+1,ri,r); //右建树 t.pushup(); } void Insert(int &r,int fa,int k) //插入到最左边 { if(r==0){ r=NewNode(fa,k); return; } //在最左边建立新节点 Insert(t.son[0],r,k); t.pushup(); } int GetMin(int r) //得到这棵子树最左端的节点 { while(t.son[0]!=0) r=t.son[0]; return r; } void Rotate(int r,int d) //翻转 { int fa=t.fa; y.son[d^1]=t.son[d]; if(t.son[d]!=0) tr[t.son[d]].fa=fa; if(y.fa==0) t.fa=0; else if(tr[y.fa].son[0]==fa) { t.fa=y.fa; tr[t.fa].son[0]=r; } else if(tr[y.fa].son[1]==fa) { t.fa=y.fa; tr[t.fa].son[1]=r; } t.son[d]=fa; y.fa=r; y.pushup(); t.pushup(); } void Splay(int r) //伸展 { while(t.fa!=0) { if(tr[t.fa].fa==0) Rotate(r,tr[t.fa].son[0]==r); else { int fa=t.fa; int d=(tr[y.fa].son[0]==fa); if(y.son[d]==r) { Rotate(r,d^1); Rotate(r,d); } else { Rotate(fa,d); Rotate(r,d); } } } } void Top(int &r,int k) //将k对应的区间置于最前面 { r=bel[k]; Splay(r);//伸展到根 int rr=GetMin(t.son[1]);//找到右子树最小的节点 Splay(rr); //伸展到根 treap& tt=tr[rr]; tt.son[0]=t.son[0]; //将原来的左子树连到rr上去 if(t.son[0]!=0) tr[t.son[0]].fa=rr; tt.fa=0; tt.pushup(); r=rr; Insert(r,0,k); //插入到最右边 Splay(r=id); //不加这个会超时。。。 } int Query(int &r,int k) { r=bel[k]; Splay(r); return t.rk(); } int Rank(int &r,int k) { int d=t.cmp(k); if(d==-1) return seg[t.v][0]+ k- tr[t.son[0]].s - 1; if(d==0) return Rank(t.son[0],k); else return Rank(t.son[1],k- t.rk()); } }spt; int main() { int T,Case=0; scanf("%d",&T); while(T--) { scanf("%d%d",&N,&Q); int k=0; A[k++]=0; for(int i=0;i<Q;i++) { scanf("%s%d",op[i],&opx[i]); if(op[i][0]==‘T‘||op[i][0]==‘Q‘) A[k++]=opx[i]; } A[k++]=N+1; sort(A,A+k); Size=0; for(int i=1;i<k;i++) //处理出每段区间 { if(A[i]==A[i-1]) continue; if(A[i]-A[i-1]>1){ seg[++Size][0]=A[i-1]+1; seg[Size][1]=A[i]-1; } seg[++Size][0]=A[i]; seg[Size][1]=A[i]; } spt.init(); spt.Build(spt.root,1,Size,0); printf("Case %d:\n",++Case); for(int i=0;i<Q;i++) { if(op[i][0]==‘T‘) spt.Top(spt.root,BIS(opx[i])); else if(op[i][0]==‘Q‘) printf("%d\n",spt.Query(spt.root,BIS(opx[i]))); else printf("%d\n",spt.Rank(spt.root,opx[i])); } } return 0; }
poj2104(主席树)
题意;查询区间内第k大的数 解析:裸的主席树,详见代码实现,我给了注释 #include<cstdio> #include<cstring> #include<string> #include<algorithm> using namespace std; const int maxn=100005; int N,M,A[maxn],B[maxn]; struct Tree { Tree *lson,*rson; int a,s; //a这个数的个数,s是<=这个数的个数 Tree(){ a=s=0; lson=rson=NULL; } void pushup() { s=a; if(lson) s+=lson->s; if(rson) s+=rson->s; } }*null=new Tree(),*r[maxn]={NULL},data[maxn*20]; int tree_id; void Update(Tree* &y,Tree* &x,int le,int ri,int v) { if(x==NULL) x=null; y=&data[++tree_id]; //创建新节点 *y=Tree(); int mid=(le+ri)/2; if(le==ri) //到达叶子节点 { *y=*x; //有可能x并不是null,而且s和a也不一定为0 y->s++; y->a++; //加1 return; } if(v<=B[mid]) //在左边 { Update(y->lson,x->lson,le,mid,v); y->rson=x->rson; //右边是完全一样的,为了节约空间 y->pushup(); //更新一下 } else //右边同理 { Update(y->rson,x->rson,mid+1,ri,v); y->lson=x->lson; y->pushup(); } } int Query(Tree* &x,Tree* &y,int le,int ri,int kth) { if(x==NULL) x=null; if(y==NULL) y=null; if(le==ri) return B[le]; //找到了 int mid=(le+ri)/2; int cnt=0; if(y->lson) cnt+=y->lson->s; //先加整段到y的 if(x->lson) cnt-=x->lson->s; //减掉到x-1的 if(cnt>=kth) return Query(x->lson,y->lson,le,mid,kth); //左边足够 else return Query(x->rson,y->rson,mid+1,ri,kth-cnt); //右边 } int main() { null->lson=null; null->rson=null;//指向自己 tree_id=0; scanf("%d%d",&N,&M); for(int i=1;i<=N;i++) { scanf("%d",&A[i]); B[i]=A[i]; } sort(B+1,B+N+1); int k=1; for(int i=2;i<=N;i++) if(B[i]!=B[k]) B[++k]=B[i]; //离散化 for(int i=1;i<=N;i++) Update(r[i],r[i-1],1,k,A[i]); //更新 for(int i=1;i<=M;i++) { int x,y,kth; //查询[x,y]第kth的数 scanf("%d%d%d",&x,&y,&kth); printf("%d\n",Query(r[x-1],r[y],1,k,kth)); } return 0; }
主席树的数组写法
int hd[maxn]; struct Tree{ int lson,rson,cnt; }; struct CMTree { int id; Tree tree[maxn*40]; void init(){ id=0; } void pushup(int rt){ e.cnt=tree[e.lson].cnt+tree[e.rson].cnt; } void Build_tree(int& rt,int le,int ri) { rt=id++; e.cnt=0; if(le==ri) return; int mid=(le+ri)/2; Build_tree(e.lson,le,mid); Build_tree(e.rson,mid+1,ri); pushup(rt); } void Update(int pre,int& rt,int le,int ri,int k,int d) { rt=id++; if(le==ri){ e.cnt=p.cnt+d; return; } int mid=(le+ri)/2; if(k<=mid) { Update(p.lson,e.lson,le,mid,k,d); e.rson=p.rson; } else { Update(p.rson,e.rson,mid+1,ri,k,d); e.lson=p.lson; } pushup(rt); } int Query(int rt,int le,int ri,int x,int y) { if(x<=le&&ri<=y) return e.cnt; int mid=(le+ri)/2; int ret=0; if(x<=mid) ret+=Query(e.lson,le,mid,x,y); if(y>mid) ret+=Query(e.rson,mid+1,ri,x,y); return ret; } }CT;
RMQ模板
//去搜一下别人给的解释 int rmq[maxn][20],Lg[maxn]; void GetLg() { Lg[0]=-1; for(int i=1;i<maxn;i++) Lg[i]=Lg[i-1]+(i&(i-1)?0:1); } int f(int x){ return 1<<x; } void ST(int N) { for(int i=0;i<N;i++) rmq[i][0]=A[i]; for(int j=1;f(j)<=N;j++) for(int i=0;i+f(j)-1<N;i++) rmq[i][j]=max(rmq[i][j-1],rmq[i+f(j-1)][j-1]); } int RMQ(int x,int y) { if(x>y) swap(x,y); int k=Lg[y-x+1]; return max(rmq[x][k],rmq[y-f(k)+1][k]); }
hdu5008(后缀数组)
题意:找到字符串中第k小的字串,并且输出下标最小的那一个。 解析:用后缀数组处理,每个后缀不同的子串个数是len-sa[i]-h[i],所以可以二分去找,但是找到了并不一定是下标最小的,所以还要继续往后面更新。 #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> using namespace std; typedef __int64 LL; typedef pair<int,int> par; const int maxn=100005; char S[maxn]; int A[maxn]; LL sum[maxn]; int wa[maxn],wb[maxn],wv[maxn],WS[maxn],sa[maxn]; bool cmp(int *r,int a,int b,int l){ return r[a]==r[b]&&r[a+l]==r[b+l]; } void DA(int *r,int n,int m) //模板 { int i,j,p; int *x=wa,*y=wb; for(i=0;i<m;i++) WS[i]=0; for(i=0;i<n;i++) WS[x[i]=r[i]]++; for(i=1;i<m;i++) WS[i]+=WS[i-1]; for(i=n-1;i>=0;i--) sa[--WS[x[i]]]=i; for(p=1,j=1;p<n;j<<=1,m=p) { for(p=0,i=n-j;i<n;i++) y[p++]=i; for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; for(i=0;i<n;i++) wv[i]=x[y[i]]; for(i=0;i<m;i++) WS[i]=0; for(i=0;i<n;i++) WS[wv[i]]++; for(i=1;i<m;i++) WS[i]+=WS[i-1]; for(i=n-1;i>=0;i--) sa[--WS[wv[i]]]=y[i]; swap(x,y); for(p=1,x[sa[0]]=0,i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rk[maxn],h[maxn]; void GetHeight(int *r,int n) { for(int i=0;i<=n;i++) rk[sa[i]]=i; int k=0; h[0]=0; for(int i=0;i<n;i++) { if(k) k--; //先减1 int j=sa[rk[i]-1];//排名在前面的 while(r[i+k]==r[j+k]) k++; //相同一直加 h[rk[i]]=k; } } int BIS(int x,int y,LL k)//找到第一个大于k的位置 { while(x<=y) { int mid=(x+y)/2; if(sum[mid]>=k) y=mid-1; else x=mid+1; } return y+1; } int main() { while(scanf("%s",S)!=EOF) { int len=strlen(S); for(int i=0;i<len;i++) A[i]=S[i]; A[len]=0; DA(A,len+1,130); //后缀数组处理 GetHeight(A,len); //得到lcp值 sum[0]=0; for(int i=1;i<=len;i++) { int a=sa[i]; int b=h[i]; sum[i]=sum[i-1]+len-a-b; //计算不同子串个数 } int Q; LL l=0,r=0,v; scanf("%d",&Q); while(Q--) { scanf("%lld",&v); LL k=(l^r^v)+1; if(k>sum[len]) printf("%lld %lld\n",l=0,r=0); //无解的情况 else { int p=BIS(1,len,k); //二分找 l=sa[p]; int sl=h[p]+k-sum[p-1]; //长度 int cur=p; while(++cur<=len&&h[cur]>=sl) l=min(l,(LL)sa[cur]);//往后面找位置最小的,不知道这种方式为何不会超时 r=l+sl-1; l++; r++; printf("%lld %lld\n",l,r); } } } return 0; }
bzoj2049(动态树lct模板题)
题意: 有三种操作,建边,毁边以及查询两个点是否相通,保证任意两个点之间最多只有一条相通的路径,也就是说整个图总是树或森林。 解析: 动态树模板题,另附两个介绍动态树的网址: https://oi.abcdabcd987.com/summary-of-link-cut-tree/ http://wenku.baidu.com/view/75906f160b4e767f5acfcedb.html #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=10005; struct lct { lct *fa,*son[2]; int rev; void pushdown() //把延迟更新的标记清除掉 { if(!rev) return; swap(son[0],son[1]); //翻转,交换左右儿子 son[0]->rev^=1; son[1]->rev^=1; rev=0; } }; struct LCT { lct data[maxn]; lct *null; void init(int Size=maxn-1) //初始化 { null=data; //null指向首元素 for(int i=0;i<=Size;i++) data[i].son[0]=data[i].son[1]=data[i].fa=null; } bool Same(lct* x,lct* &y) //判断x和x的父亲是否在同一树里 { return (y=x->fa)!=null&&(y->son[0]==x||y->son[1]==x); } void Rotate(lct* x,int d) //翻转 { lct* y=x->fa; //x的父亲 y->son[d^1]=x->son[d]; if(x->son[d]!=null) x->son[d]->fa=y; //x的子节点的父亲指向y x->fa=y->fa; //连接 if(y->fa->son[0]==y) x->fa->son[0]=x; else if(y->fa->son[1]==y) x->fa->son[1]=x; x->son[d]=y; y->fa=x; } void Splay(lct* x) { x->pushdown(); //清除标记 lct* y; while(Same(x,y)) //没有到树的最顶点 { y->pushdown(); x->pushdown(); Rotate(x,y->son[0]==x); //翻转 } } lct* Access(lct* u) //打通路径 { lct *v=null; for(;u!=null;u=u->fa) { Splay(u); u->son[1]=v; v=u; } return v; } lct* GetRoot(lct* x) //得到根 { for(x=Access(x);x->pushdown(),x->son[0]!=null;x=x->son[0]); return x; } void MakeRoot(lct* x) //使x成为根 { Access(x)->rev^=1; Splay(x); } void Link(lct* x,lct* y) //连接两个点 { MakeRoot(x); x->fa=y; Access(x); } void Cut(lct* x,lct* y) //断开两个点 { MakeRoot(x); Access(y); Splay(y); y->son[0]->fa=null; y->son[0]=null; } }A; int N,M; int main() { scanf("%d%d",&N,&M); A.init(N); int x,y; char S[10]; while(M--) { scanf("%s%d%d",S,&x,&y); if(S[0]==‘Q‘) { lct *a=A.GetRoot(A.data+x); lct *b=A.GetRoot(A.data+y); if(a!=A.null&&a==b) printf("Yes\n"); else printf("No\n"); } else if(S[0]==‘D‘) A.Cut(A.data+x,A.data+y); else A.Link(A.data+x,A.data+y); } return 0; }
spoj QTREE(树链剖分)
题意:给出一棵树,有两种操作,一种是修改边权,另一种是查询两个点之间最大边权。 解析:裸的树链剖分,用线段树维护区间最大值。 #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<cmath> #include<vector> using namespace std; #define tu nod[u] #define tv nod[v] #define e tree[id] #define lson tree[id*2] #define rson tree[id*2+1] const int maxn=10005; int N,pid; vector<int> G[maxn]; struct node { int top,fa,deep;//top所属树链的顶点,fa父节点,deep深度 int s,p,fp,son; //s以它为子树的大小,p新编号,fp相对p反向的,这题里面没什么用,son重链所指向的点 }nod[maxn]; struct edge { int u,v,c; edge(int u=0,int v=0,int c=0):u(u),v(v),c(c){} }E[maxn]; void init() { pid=0; for(int i=0;i<maxn;i++) G[i].clear(),nod[i].son=-1; //初始化 } void dfs(int u,int fa,int deep) { tu.fa=fa,tu.deep=deep,tu.s=1; //保存父节点,深度,大小为1 int Size=G[u].size(); for(int i=0;i<Size;i++) { int v=G[u][i]; if(v==fa) continue; //父亲不管 dfs(v,u,deep+1); tu.s+=tv.s; //加上大小 if(tu.son==-1||tv.s>nod[tu.son].s) tu.son=v; //找重链所指向的点 } } void Div(int u,int top) { tu.top=top; //重链顶点 tu.p=++pid; //重新编号 nod[tu.p].fp=u; if(tu.son!=-1) Div(tu.son,top); //有重链继续往下找 else return; int Size=G[u].size(); for(int i=0;i<Size;i++) { int v=G[u][i]; if(v==tu.fa||v==tu.son) continue; Div(v,v); //新的重链 } } struct Tree //线段树维护区间最大值 { int le,ri,v; }tree[4*maxn]; void pushup(int id){ e.v=max(lson.v,rson.v); } void Build_tree(int id,int le,int ri) { e.le=le,e.ri=ri,e.v=0; if(le==ri) return; int mid=(le+ri)/2; Build_tree(id*2,le,mid); Build_tree(id*2+1,mid+1,ri); } void Update(int id,int k,int v) { int le=e.le,ri=e.ri; if(le==ri){ e.v=v; return; } int mid=(le+ri)/2; if(k<=mid) Update(id*2,k,v); else Update(id*2+1,k,v); pushup(id); } int Query(int id,int x,int y) { int le=e.le,ri=e.ri; if(x<=le&&ri<=y) return e.v; int mid=(le+ri)/2; int ret=0; if(x<=mid) ret=max(ret,Query(id*2,x,y)); if(y>mid) ret=max(ret,Query(id*2+1,x,y)); return ret; } int Find(int u,int v) { int f1=tu.top,f2=tv.top; int ret=0; while(f1!=f2) //不在同一条链上 { if(nod[f1].deep<nod[f2].deep) //总是让u是深度大的点 { swap(f1,f2); swap(u,v); } ret=max(ret,Query(1,nod[f1].p,tu.p)); //查询这条链 u=nod[f1].fa; f1=tu.top; //跳到父节点去 } if(u==v) return ret; //相同 if(tu.deep>tv.deep) swap(u,v); ret=max(ret,Query(1,nod[tu.son].p,tv.p)); //查询同一条链上的这段区间 return ret; } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&N); init(); for(int i=1;i<N;i++) { int u,v,c; scanf("%d%d%d",&u,&v,&c); E[i]=edge(u,v,c); //边 G[u].push_back(v); G[v].push_back(u); } dfs(1,0,0); Div(1,1); Build_tree(1,1,pid); //建树 for(int i=1;i<N;i++) { edge& t=E[i]; int& u=t.u; int& v=t.v; if(tu.deep>tv.deep) swap(u,v); Update(1,tv.p,t.c); //修改边权 } char op[15]; while(scanf("%s",op)!=EOF) { int u,v; if(op[0]==‘D‘) break; scanf("%d%d",&u,&v); if(op[0]==‘Q‘) printf("%d\n",Find(u,v)); //查询 else Update(1,nod[E[u].v].p,v); //更新 } } return 0; }
搜索
精确覆盖DLX算法模板
struct DLX { int n,id; int L[maxn],R[maxn],U[maxn],D[maxn]; int C[maxn],S[maxn],loc[maxn][2]; int H[ms]; void init(int nn=0) //传列长 { n=nn; for(int i=0;i<=n;i++) U[i]=D[i]=i,L[i]=i-1,R[i]=i+1; L[0]=n; R[n]=0; id=n; memset(S,0,sizeof(S)); memset(H,-1,sizeof(H)); } void Link(int x,int y) { ++id; D[id]=y; U[id]=U[y]; D[U[y]]=id; U[y]=id; loc[id][0]=x,loc[id][1]=y; C[id]=y; S[y]++; if(H[x]==-1) H[x]=L[id]=R[id]=id; else { int a=H[x]; int b=R[a]; L[id]=a; R[a]=id; R[id]=b; L[b]=id; H[x]=id; } } void Remove(int c) { L[R[c]]=L[c]; R[L[c]]=R[c]; for(int i=D[c];i!=c;i=D[i]) for(int j=R[i];j!=i;j=R[j]) { U[D[j]]=U[j]; D[U[j]]=D[j]; S[C[j]]--; } } void Resume(int c) { for(int i=U[c];i!=c;i=U[i]) for(int j=R[i];j!=i;j=R[j]) { S[C[j]]++; U[D[j]]=j; D[U[j]]=j; } L[R[c]]=c; R[L[c]]=c; } bool dfs(int step) { if(step>=N) return true; if(R[0]==0) return false; int Min=INF,c=-1; for(int i=R[0];i;i=R[i]) if(Min>S[i]){ Min=S[i]; c=i; } Remove(c); for(int i=D[c];i!=c;i=D[i]) { //ans[step]=loc[i][0]; for(int j=R[i];j!=i;j=R[j]) Remove(C[j]); if(dfs(step+1)) return true; for(int j=L[i];j!=i;j=L[j]) Resume(C[j]); } Resume(c); return false; } }dlx;
spoj1771(N皇后精确覆盖DLX)
题意:N皇后问题,但是棋盘已经有一些皇后,然后问如何选择皇后的位置使得他们互不攻击,N<=50 解析:N这么大,显然不可能爆搜,这里用到的是精确覆盖DLX算法,行表示每个点,DLX的列对应棋盘的行,列和两个方向的斜对角。 搜到N步就可以了,考虑行就好了,不用考虑斜对角什么的。 #include<cstdio> #include<cstring> #include<string> #include<algorithm> using namespace std; const int INF=1e9+7; const int ms=51*51; const int maxn=ms*5; int N,ans[55],res[55];//ans存储第几个选择的编号,res保存第几行答案是第几列 struct DLX { int n,id; int L[maxn],R[maxn],U[maxn],D[maxn]; int C[maxn],S[maxn],loc[maxn][2]; int H[ms]; void init(int nn=0) //传列长 { n=nn; for(int i=0;i<=n;i++) U[i]=D[i]=i,L[i]=i-1,R[i]=i+1; L[0]=n; R[n]=0; id=n; memset(S,0,sizeof(S)); memset(H,-1,sizeof(H)); } void Link(int x,int y) { ++id; D[id]=y; U[id]=U[y]; D[U[y]]=id; U[y]=id; loc[id][0]=x,loc[id][1]=y; C[id]=y; S[y]++; if(H[x]==-1) H[x]=L[id]=R[id]=id; else { int a=H[x]; int b=R[a]; L[id]=a; R[a]=id; R[id]=b; L[b]=id; H[x]=id; } } void Remove(int c) { L[R[c]]=L[c]; R[L[c]]=R[c]; for(int i=D[c];i!=c;i=D[i]) for(int j=R[i];j!=i;j=R[j]) { U[D[j]]=U[j]; D[U[j]]=D[j]; S[C[j]]--; } } void Resume(int c) { for(int i=U[c];i!=c;i=U[i]) for(int j=R[i];j!=i;j=R[j]) { S[C[j]]++; U[D[j]]=j; D[U[j]]=j; } L[R[c]]=c; R[L[c]]=c; } bool dfs(int step) { if(step>=N) return true; if(R[0]==0) return false; int Min=INF,c=-1; for(int i=R[0];i;i=R[i]) { if(i>N) break; if(Min>S[i]){ Min=S[i]; c=i; } } if(c==-1) return false; Remove(c); for(int i=D[c];i!=c;i=D[i]) { ans[step]=loc[i][0]; for(int j=R[i];j!=i;j=R[j]) Remove(C[j]); if(dfs(step+1)) return true; for(int j=L[i];j!=i;j=L[j]) Resume(C[j]); } Resume(c); return false; } }dlx; bool vis[55*6]; int main() { while(scanf("%d",&N)!=EOF) { dlx.init(N*6-2); memset(vis,false,sizeof(vis)); int y; for(int x=1;x<=N;x++) { scanf("%d",&y); if(y==0) continue; int a=x,b=N+y,c=2*N+N+x-y,d=4*N+x+y-2; //对应的行列斜对角编号 vis[a]=vis[b]=vis[c]=vis[d]=true; //标记 int t=(x-1)*N+y-1; dlx.Link(t,a); //连接 dlx.Link(t,b); dlx.Link(t,c); dlx.Link(t,d); } for(int x=1;x<=N;x++) for(int y=1;y<=N;y++) { int a=x,b=N+y,c=2*N+N+x-y,d=4*N+x+y-2; if(vis[a]||vis[b]||vis[c]||vis[d]) continue; //有被占据不考虑 int t=(x-1)*N+y-1; dlx.Link(t,a); dlx.Link(t,b); dlx.Link(t,c); dlx.Link(t,d); } if(!dlx.dfs(0)) printf("No answer find\n"); else { for(int i=0;i<N;i++) res[ans[i]/N]=ans[i]%N; for(int i=0;i<N;i++) printf("%d%c",res[i]+1,i==N-1?‘\n‘:‘ ‘); } } return 0; }
poj3074(数独问题精确覆盖DLX)
题意: 给出一个9*9的矩阵,有一些格子已经填了数,有一些是.代表未填。求任意一组解使得每行包含1~9,每列包含1~9,每个小矩形(3*3)包含1~9。 解析: 精确覆盖DLX的经典题目,每一行代表要填数的情况,列共有81*4行,第一个81行代表第i行j列放了数,第二个81列代表第i行放的数k,第三个81列 代表第j列放得数k,第四个81行代表第i个小矩形放的数k。对于字符为.的情况添加9行,对于字符为数字的情况添加一行。然后就是跑一边DLX,保存一下答案 输出即可。 #include<cstdio> #include<cstring> #include<string> #include<algorithm> using namespace std; const int INF=1e9+7; const int ms=81*10; const int maxn=ms*4; int ans[maxn]; struct DLX { int n,id; int L[maxn],R[maxn],U[maxn],D[maxn]; int C[maxn],S[maxn],loc[maxn][3]; int H[ms]; void init(int nn=0) { n=nn; for(int i=0;i<=n;i++) U[i]=D[i]=i,L[i]=i-1,R[i]=i+1; L[0]=n; R[n]=0; id=n; memset(S,0,sizeof(S)); memset(H,-1,sizeof(H)); } void Link(int x,int y,int px,int py,int k) { ++id; D[id]=y; U[id]=U[y]; D[U[y]]=id; U[y]=id; loc[id][0]=px,loc[id][1]=py,loc[id][2]=k; C[id]=y; S[y]++; if(H[x]==-1) H[x]=L[id]=R[id]=id; else { int a=H[x]; int b=R[a]; L[id]=a; R[a]=id; R[id]=b; L[b]=id; H[x]=id; } } void Remove(int c) { L[R[c]]=L[c]; R[L[c]]=R[c]; for(int i=D[c];i!=c;i=D[i]) for(int j=R[i];j!=i;j=R[j]) { U[D[j]]=U[j]; D[U[j]]=D[j]; S[C[j]]--; } } void Resume(int c) { for(int i=U[c];i!=c;i=U[i]) for(int j=R[i];j!=i;j=R[j]) { S[C[j]]++; U[D[j]]=j; D[U[j]]=j; } L[R[c]]=c; R[L[c]]=c; } bool dfs(int step) { if(step==81) return true; if(R[0]==0) return false; int Min=INF,c=-1; for(int i=R[0];i;i=R[i]) if(Min>S[i]){ Min=S[i]; c=i; } Remove(c); for(int i=D[c];i!=c;i=D[i]) { ans[step]=i; for(int j=R[i];j!=i;j=R[j]) Remove(C[j]); if(dfs(step+1)) return true; for(int j=L[i];j!=i;j=L[j]) Resume(C[j]); } Resume(c); return false; } }dlx; int main() { char S[90]; while(scanf("%s",S)!=EOF) { if(S[0]==‘e‘) break; dlx.init(81*4); int k=0,r=0; for(int x=0;x<9;x++) for(int y=0;y<9;y++) { char ch=S[k++]; int a,b,c,d; if(ch==‘.‘) { for(int i=1;i<=9;i++) { a=x*9+y+1; b=x*9+i+81; c=y*9+i+81+81; int s=(x/3)*3+y/3; d=s*9+i+81+81+81; ++r; dlx.Link(r,a,x,y,i); dlx.Link(r,b,x,y,i); dlx.Link(r,c,x,y,i); dlx.Link(r,d,x,y,i); } } else { int i=ch-‘0‘; a=x*9+y+1; b=x*9+i+81; c=y*9+i+81+81; int s=(x/3)*3+y/3; d=s*9+i+81+81+81; ++r; dlx.Link(r,a,x,y,i); dlx.Link(r,b,x,y,i); dlx.Link(r,c,x,y,i); dlx.Link(r,d,x,y,i); } } dlx.dfs(0); int res[10][10]; for(int i=0;i<81;i++) { int a=ans[i]; int x=dlx.loc[a][0],y=dlx.loc[a][1],k=dlx.loc[a][2]; res[x][y]=k; } for(int i=0;i<9;i++) for(int j=0;j<9;j++) printf("%d",res[i][j]); printf("\n"); } return 0; }
图论
hdu5772(最大权闭合子图问题,网络流)
解析: 多校标答 第一类:Pij 表示第i个点和第j个点组合的点,那么Pij的权值等于w[i][j]+w[j][i](表示得分) 第二类:原串中的n个点每个点拆出一个点,第i个点权值为 –a[s[i]] (表示要花费) 第三类:对于10种字符拆出10个点,每个点的权值为 -(b[x]-a[x]) 最大权闭合图用网络流求解,根据原图建立一个等价的网络,构建规则如下。 对于点权为正的节点,从源点连一条容量为点权的边到该点,对于点权为负的边,从该点连一条容量为点权绝对值的边到汇点。原图中的边保留,容量为inf。最大权值即为图中所有正点权之和减去最大流。 这题我看了半天才理解。。。。。 #include<cstdio> #include<cstring> #include<vector> #include<queue> #include<algorithm> using namespace std; const int INF=1e9+7; const int maxn=102; const int maxnode=maxn*maxn; int N,a[10],b[10],w[maxn][maxn]; int eid,S,T; //eid用于边的编号,S,T分别为源点汇点 vector<int> G[maxnode]; //存储边的编号 struct edge { int u,v,cap,flow; //头,尾,容量和流量 edge(int u=0,int v=0,int cap=0,int flow=0):u(u),v(v),cap(cap),flow(flow){} }E[maxnode*10]; void AddEdge(int u,int v,int c) //建边 { E[eid]=edge(u,v,c,0); //正向边 G[u].push_back(eid); eid++; E[eid]=edge(v,u,0,0); //反向边 G[v].push_back(eid); eid++; } bool vis[maxnode]; int d[maxnode],cur[maxnode]; queue<int> que; bool BFS() { memset(vis,false,sizeof(vis)); while(!que.empty()) que.pop(); vis[S]=true; d[S]=0; que.push(S); while(!que.empty()) { int u=que.front(); que.pop(); int Size=G[u].size(); for(int i=0;i<Size;i++) { int id=G[u][i]; edge& e=E[id]; int v=e.v; if(!vis[v]&&e.cap>e.flow) { vis[v]=true; d[v]=d[u]+1; que.push(v); } } } return vis[T]; } int DFS(int u,int a) //分层 { if(u==T||a==0) return a; int ret=0,f; int Size=G[u].size(); for(int& i=cur[u];i<Size;i++) { int id=G[u][i]; edge& e=E[id]; int v=e.v; if(d[u]+1==d[v]&&(f=DFS(v,min(a,e.cap-e.flow)))>0) { ret+=f; e.flow+=f; E[id^1].flow-=f; a-=f; if(a==0) break; } } return ret; } int MaxFlow(int s,int t) //最大流Dinic算法 { S=s; T=t; int ret=0; while(BFS()) { memset(cur,false,sizeof(cur)); ret+=DFS(S,INF); } return ret; } int main() { int TT,Case=0; scanf("%d",&TT); while(TT--) { char SS[maxn]; scanf("%d",&N); scanf("%s",SS); for(int i=0;i<10;i++) scanf("%d%d",&a[i],&b[i]); int be=N*N+N+10; int en=be+1; for(int i=0;i<=en;i++) G[i].clear(); eid=0; int ans=0; for(int i=0;i<N;i++) for(int j=0;j<N;j++) { scanf("%d",&w[i][j]); if(i==j) continue; ans+=w[i][j]; AddEdge(be,i*N+j,w[i][j]); AddEdge(i*N+j,N*N+i,INF); AddEdge(i*N+j,N*N+j,INF); } for(int i=0;i<N;i++) AddEdge(N*N+i,N*N+N+SS[i]-‘0‘,INF); for(int i=0;i<N;i++) AddEdge(N*N+i,en,a[SS[i]-‘0‘]); for(int i=0;i<10;i++) AddEdge(N*N+N+i,en,b[i]-a[i]); printf("Case #%d: %d\n",++Case,ans-MaxFlow(be,en)); } return 0; }
zoj3332(有向竞赛图)
解析: 裸的有向竞赛图,有向竞赛图要满足的条件是有N*(N-1)/2条边,也就是每个点与其他点都有边相连,求哈密顿通路。 代码 #include<cstdio> #include<cstring> #include<string> #include<iostream> #include<sstream> #include<algorithm> #include<utility> #include<vector> #include<set> #include<map> #include<queue> #include<cmath> #include<iterator> #include<stack> using namespace std; const int maxn=105; int N,ans[maxn]; bool link[maxn][maxn]; int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&N); memset(link,false,sizeof(link)); int tot=N*(N-1)/2; int u,v; for(int i=1;i<=tot;i++) { scanf("%d%d",&u,&v); link[u][v]=true; } ans[1]=1; int i,j,k; for(k=2;k<=N;k++) { for(j=1;j<k;j++) if(link[k][ans[j]]) break; for(i=k;i>j;i--) ans[i]=ans[i-1]; ans[j]=k; } for(int i=1;i<=N;i++) printf("%d%c",ans[i],i==N?‘\n‘:‘ ‘); } return 0; }
uva11183(有向图最小生成树朱刘算法)
解析: 裸的有向图最小生成树 #include<cstdio> #include<cstring> #include<string> #include<iostream> #include<sstream> #include<algorithm> #include<utility> #include<vector> #include<set> #include<map> #include<queue> #include<cmath> #include<iterator> #include<stack> using namespace std; const int INF=1e9+7; const int eps=0.0000001; const int maxn=1005; struct edge { int u,v,w; edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){} }E[40005]; int pre[maxn],InEdge[maxn],vis[maxn],id[maxn]; int Dir_MST(int root,int Vcnt,int Ecnt) { int ret=0; while(true) { for(int i=1;i<=Vcnt;i++) InEdge[i]=INF; for(int i=1;i<=Ecnt;i++) { edge& e=E[i]; int u=e.u,v=e.v,w=e.w; if(u==v) continue; if(w<InEdge[v]) { InEdge[v]=w; pre[v]=u; } //找最小的指向v的边 } InEdge[root]=0; for(int i=1;i<=Vcnt;i++) if(i!=root&&InEdge[i]==INF) return -1;//存在某个点跟整个图分离 int ID=0; for(int i=1;i<=Vcnt;i++) vis[i]=id[i]=-1; for(int i=1;i<=Vcnt;i++) { ret+=InEdge[i]; //把那些边加进答案 int a=i; while(vis[a]!=i&&id[a]==-1&&a!=root){ vis[a]=i; a=pre[a]; } if(id[a]==-1&&a!=root) { ++ID; for(int b=pre[a];b!=a;b=pre[b]) id[b]=ID; //重新编号 id[a]=ID; } } if(ID==0) return ret; //找到解 for(int i=1;i<=Vcnt;i++) if(id[i]==-1) id[i]=++ID; //独立的点编号 for(int i=1;i<=Ecnt;i++) { edge& e=E[i]; int u=e.u,v=e.v; e.u=id[u]; e.v=id[v]; if(id[u]!=id[v]) e.w-=InEdge[v]; //之前加的那一部分要减掉 } Vcnt=ID; root=id[root]; } } int main() { int T,Case=0; scanf("%d",&T); while(T--) { int N,M,u,v,w; scanf("%d%d",&N,&M); for(int i=1;i<=M;i++) { scanf("%d%d%d",&u,&v,&w); u++; v++; E[i]=edge(u,v,w); } int ans=Dir_MST(1,N,M); printf("Case #%d: ",++Case); if(ans==-1) printf("Possums!\n"); else printf("%d\n",ans); } return 0; }
强连通分量
#include<cstdio> #include<cstring> #include<algorithm> #include<stack> #include<vector> using namespace std; const int maxn=10005; vector<int> G[maxn]; int N,M,ans; bool inq[maxn]; //是否在栈内 int nid,dfn[maxn],low[maxn]; //dfn相当于重新编号,low能搜到的最小编号 stack<int> KK; //栈 void init() //初始化 { ans=nid=0; for(int i=0;i<=N;i++) { inq[i]=false; dfn[i]=low[i]=0; } while(!KK.empty()) KK.pop(); } void Tarjan(int u) { dfn[u]=low[u]=++nid; //编号 KK.push(u); inq[u]=true; //丢进栈里 int Size=G[u].size(); for(int i=0;i<Size;i++) { int v=G[u][i]; if(!dfn[v]) //没有访问过 { Tarjan(v); low[u]=min(low[u],low[v]); } else if(inq[v]) low[u]=min(low[u],dfn[v]); //访问过在栈里 } if(dfn[u]==low[u])//相等说明这是一个连通分量 { ans++; int v; do { v=KK.top(); KK.pop(); inq[v]=false; }while(v!=u); } } int main() { while(scanf("%d%d",&N,&M)!=EOF) { if(!N&&!M) break; for(int i=0;i<=N;i++) G[i].clear(); int u,v; while(M--) { scanf("%d%d",&u,&v); G[u].push_back(v); } init(); for(int i=1;i<=N;i++) if(!dfn[i]) Tarjan(i); if(ans<=1) printf("Yes\n"); else printf("No\n"); } return 0; }
hdu3072(强连通求最小值)
题意:简单点说就是求把所有强连通分量连在一起所需的最小花费 解析:先把所有强连通分量求出来,再求不同连通分量连接起来的最小花费,最后把除0所在的连通分量所需的最小花费连接起来, #include<cstdio> #include<cstring> #include<string> #include<iostream> #include<sstream> #include<algorithm> #include<utility> #include<vector> #include<set> #include<map> #include<queue> #include<cmath> #include<iterator> #include<stack> using namespace std; const int INF=1e9+7; const int eps=0.0000001; typedef __int64 LL; const int maxn=50005; const int maxm=100005; int N,M,ans,id; int dfn[maxn],low[maxn],cost[maxn],resign[maxn]; vector<int> G[maxn]; stack<int> KK; bool inq[maxn]; struct edge { int u,v,w; edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){} }E[maxm]; void init() { ans=id=0; while(!KK.empty()) KK.pop(); for(int i=0;i<=N;i++) { dfn[i]=low[i]=0; resign[i]=0; cost[i]=INF; G[i].clear(); inq[i]=false; } } void Tarjan(int x) { dfn[x]=low[x]=++id; inq[x]=true; KK.push(x); int t,Size=G[x].size(); for(int i=0;i<Size;i++) { t=G[x][i]; if(!dfn[t]) { Tarjan(t); low[x]=min(low[x],low[t]); } else if(inq[t]) low[x]=min(low[x],dfn[t]); } //前面都差不多 if(dfn[x]==low[x]) { ans++; do { t=KK.top(); KK.pop(); inq[t]=false; resign[t]=ans; //这个地方,标记连通分量 }while(t!=x); } return; } int main() { while(scanf("%d%d",&N,&M)!=EOF) { init(); int u,v,w; for(int i=1;i<=M;i++) { scanf("%d%d%d",&u,&v,&w); E[i]=edge(u,v,w); G[u].push_back(v); //有向图 } for(int i=0;i<N;i++) if(!dfn[i]) Tarjan(i); for(int i=1;i<=M;i++) { edge& e=E[i]; int u=e.u,v=e.v,w=e.w; int x=resign[u],y=resign[v]; //连通分量繁荣编号 if(x!=y) cost[y]=min(cost[y],w); //更新值 } int sum=0; for(int i=1;i<=ans;i++) { if(i==resign[0]||cost[i]==INF) continue;//跟0是统一连通分量的不管 sum+=cost[i]; } printf("%d\n",sum); } return 0; }
hdu4005(双连通缩点)
题意: 敌人在N个点间建了M条线路,每条线路都有一个权值,敌人要再建一条线路,己方可以毁掉敌方的一条线路,问己方最少要花多少钱(至少得准备多少钱)才能使 这些点不连通。 解析: 先分离出每个双连通分量(删除双联通分量中的任何一条边还是连通的,没有意义),给每个分量重新编一个号,缩成一个点,在Tarjan算法过程中把桥保存下来。 选一条权值最小的桥,分别从两头出发,找次小的桥。答案就是次小的桥的权值,为甚么是找次小的桥,因为如果敌人是把最小的桥所连的两个连通分量再加一条边,则 必须删次小的桥,如果是其他,则只需要删最小的桥即可。 #include<cstdio> #include<cstring> #include<string> #include<iostream> #include<sstream> #include<algorithm> #include<utility> #include<vector> #include<set> #include<map> #include<queue> #include<cmath> #include<iterator> #include<stack> using namespace std; typedef __int64 LL; const int INF=1e9+7; const double eps=1e-7; const int maxn=10005; const int maxm=100005; int N,M,eid,id,top; int scc_id,scc[maxn],dfn[maxn],low[maxn],b[maxn],fa[maxn]; int head[maxn],KK[maxn]; struct edge { int v,w,next; edge(int v=0,int w=0,int next=-1):v(v),w(w),next(next){} }E[2*maxm]; struct node { int u,v,w; node(int u=0,int v=0,int w=0):u(u),v(v),w(w){} }; vector<node> V; void init() { eid=id=top=scc_id=0; for(int i=0;i<=N;i++) { head[i]=-1; dfn[i]=low[i]=b[i]=scc[i]=0; fa[i]=i; } V.clear(); } void AddEdge(int u,int v,int w) { E[++eid]=edge(v,w,head[u]); head[u]=eid; } void Tarjan(int u) { dfn[u]=low[u]=++id; KK[top++]=u; bool first=false; for(int i=head[u];i!=-1;i=E[i].next) { edge& e=E[i]; int v=e.v,w=e.w; if(v==fa[u]&&!first){ first=true; continue; } if(!dfn[v]) { fa[v]=u; Tarjan(v); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]) V.push_back(node(u,v,w));//这条边是桥 } else low[u]=min(low[u],dfn[v]); } int t; if(dfn[u]==low[u]) { scc_id++; //双连通分量 do { t=KK[--top]; b[t]=scc_id; //连通分量编号 }while(u!=t); } return; } int ans; int FindPath(int u,int pre) { int Min=INF,MMin=INF; for(int i=head[u];i!=-1;i=E[i].next) { int v=E[i].v,w=E[i].w; if(v==pre) continue; int t=FindPath(v,u); if(t<MMin) MMin=t; if(w<MMin) MMin=w; if(Min>MMin) swap(Min,MMin); } ans=min(ans,MMin); return Min; } int main() { while(scanf("%d%d",&N,&M)!=EOF) { init(); int u,v,w; for(int i=1;i<=M;i++) { scanf("%d%d%d",&u,&v,&w); AddEdge(u,v,w); //建边 AddEdge(v,u,w); //反向边 } for(int i=1;i<=N;i++) if(!dfn[i]) Tarjan(i); //找连通分量 int mindist=INF,picku,pickv,Size=V.size(); eid=0; memset(head,-1,sizeof(head)); for(int i=0;i<Size;i++) //重新建图 { node& t=V[i]; int u=t.u,v=t.v,w=t.w; AddEdge(b[u],b[v],w); AddEdge(b[v],b[u],w); if(w<mindist){ mindist=w,picku=b[u],pickv=b[v]; } //权值最小的桥 } ans=INF; FindPath(picku,pickv); FindPath(pickv,picku); if(ans==INF) printf("-1\n"); else printf("%d\n",ans); } return 0; }
标签:
原文地址:http://www.cnblogs.com/wustacm/p/5754362.html