http://poj.org/problem?id=2299
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #define lowbit(num) (num&(-num)) #define MAXN 500100 using namespace std; struct point { int num,val; //元素的编号与值 bool operator<(const point &b)const{return val<b.val;} }in[MAXN]; int sum[MAXN]; int num[MAXN]; void update(int x,int val) //更新点x,在点x基础上加val { while(x<MAXN) { sum[x]+=val; x+=lowbit(x); } } int query(int x) //查询前x个元素的和 { int ans=0; while(x>0) { ans+=sum[x]; x-=lowbit(x); } return ans; } int main() { int n; while(cin>>n&&n) { memset(sum,0,sizeof(sum)); memset(num,0,sizeof(num)); for(int i=1;i<=n;i++) { scanf("%d",&in[i].val); in[i].num=i; } sort(in+1,in+n+1); for(int i=1;i<=n;i++) //离散化 num[i]=in[i].num; long long int ans=0; for(int i=n;i>=1;i--) { ans+=query(num[i]); update(num[i],1); } cout<<ans<<endl; } return 0; }
2、POJ 3067 Japan
http://poj.org/problem?id=3067
题目大意:日本岛东海岸有n个火车站,西海岸有m个火车站,在东西海岸修了k条高铁,给出每条高铁连接的东西海岸火车站编号,求有多少条高铁相交
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 1000010 #define lowbit(x) (x&(-x)) using namespace std; struct Line { int x,y; }lines[MAXN]; long long int sum[MAXN]; int n,m,k; bool cmp(Line a,Line b) { if(a.x!=b.x) return a.x<b.x; return a.y<b.y; } void update(int x,int val) { while(x<=m) { sum[x]+=val; x+=lowbit(x); } } long long int query(int x) { long long int ans=0; while(x>0) { ans+=sum[x]; x-=lowbit(x); } return ans; } int main() { int TestCase; scanf("%d",&TestCase); for(int Case=1;Case<=TestCase;Case++) { long long int ans=0; memset(sum,0,sizeof(sum)); scanf("%d%d%d",&n,&m,&k); for(int i=0;i<k;i++) scanf("%d%d",&lines[i].x,&lines[i].y); sort(lines,lines+k,cmp); for(int i=0;i<k;i++) //下面是逆序对求解过程 { ans+=i-query(lines[i].y); update(lines[i].y,1); } printf("Test case %d: %lld\n",Case,ans); } return 0; }
(二)树状数组求前缀和
2、POJ 2481 Cows
http://poj.org/problem?id=2481
题意:有n头牛,每头牛有强壮指数(si,ei),若si<sj且ei>ej,则表明牛i比牛j强壮,现在给出一些牛的强壮指数,求每头牛比其他多少头牛强壮
首先将这些牛按照e进行降序排序,这样我们在判定牛是否强壮时只需对比s即可,比牛i弱小的牛j,其sj∈[0,i),所以我们只需要用树状数组维护sum[i],sum[i]表示落在[0,i]上的s值个数,对于两头牛s、e均相同的情况需要单独考虑,累加这些相同的强壮指数,在得到sum[i]后扣除这些强壮指数完全相同的牛的个数
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 100010 #define INF 0x3f3f3f3f #define lowbit(ans) (ans&(-ans)) using namespace std; struct Cow { int s,e,id; }pre,cows[MAXN]; //保存牛参数的结构体 int ans[MAXN],sum[MAXN],n; bool cmp(Cow a,Cow b) { if(a.e!=b.e) return a.e>b.e; return a.s<b.s; } void update(int x,int val) //对点x增加值val { while(x<MAXN) { sum[x]+=val; x+=lowbit(x); } } int query(int x) { int tot=0; while(x>0) { tot+=sum[x]; x-=lowbit(x); } return tot; } int main() { while(1) { memset(ans,0,sizeof(ans)); memset(sum,0,sizeof(sum)); scanf("%d",&n); if(!n) break; for(int i=1;i<=n;i++) { scanf("%d%d",&cows[i].s,&cows[i].e); cows[i].s++,cows[i].e++; cows[i].id=i; } sort(cows+1,cows+n+1,cmp); //对牛按第一关键字e降序,第二关键字s升序 pre.s=pre.e=-1; //pre=排序后在当前牛之前的一头牛 int cnt=0; for(int i=1;i<=n;i++) { if(cows[i].s==pre.s&&cows[i].e==pre.e) //当前牛和之前的一头牛区间相同 cnt++; else { cnt=0; pre.s=cows[i].s; pre.e=cows[i].e; } ans[cows[i].id]=query(cows[i].s)-cnt; update(cows[i].s,1); } for(int i=1;i<=n;i++) printf("%d ",ans[i]); printf("\n"); } return 0; }
3、POJ 2182 lost Cows(树状数组+二分答案)
http://poj.org/problem?id=2182
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #define lowbit(num) (num&(-num)) #define MAXN 8100 using namespace std; int in[MAXN],n; int sum[MAXN]; int ans[MAXN]; void update(int x,int val) //对点x更新,增加val { while(x<MAXN) { sum[x]+=val; x+=lowbit(x); } } int query(int x) //查询前x个点的和 { int ans=0; while(x>0) { ans+=sum[x]; x-=lowbit(x); } return ans; } int binarySearch(int x) //二分查找 { int lowerBound=1,upperBound=n,mid; while(lowerBound<upperBound) { mid=(lowerBound+upperBound)/2; if(mid-query(mid)<x) //x之前比x小的数不够多,则增大下界 lowerBound=mid+1; else upperBound=mid; //否则减小上界 } return upperBound; } int main() { scanf("%d",&n); for(int i=2;i<=n;i++) scanf("%d",&in[i]); in[1]=0; for(int i=n;i>=1;i--) //尝试填第i位的数 { int pos=binarySearch(in[i]+1); ans[i]=pos; update(pos,1); } for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
4、POJ 2352 Stars
http://poj.org/problem?id=2352
题意:给出一组点的坐标(xi,yi),输入的坐标顺序按照第一关键字y升序,第二关键字x升序,若xj<=xi且yj<=yi,则点(xj,yj)比(xi,yi)小,level[x]=比自己小的点的个数为x的点的个数,求level[i],i∈[0,n)
这个题有点类似于POJ 2481 Cows,不过此题简单些,由于题目给出的点是有序的,所以我们只需要在每次输入一个点时维护一个线段数组sum,sum[x]=横坐标小于等于x的点的个数,比点(xi,yi)小的点的个数即为sum[xi]
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #define MAXN 32005 #define lowbit(x) (x&(-x)) using namespace std; int level[MAXN],sum[MAXN]; void update(int x,int val) { while(x<MAXN) { sum[x]+=val; x+=lowbit(x); } } int query(int x) { int ans=0; while(x>0) { ans+=sum[x]; x-=lowbit(x); } return ans; } int main() { int n; while(scanf("%d",&n)!=EOF) { memset(sum,0,sizeof(sum)); memset(level,0,sizeof(level)); for(int i=1;i<=n;i++) { int x,y; scanf("%d%d",&x,&y); x++; level[query(x)]++; update(x,1); } for(int i=0;i<n;i++) printf("%d\n",level[i]); } return 0; }
1、POJ 3468 A Simple Problem with Integers(裸线段树区间修改&区间求和)
http://poj.org/problem?id=3468
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #define MAXN 200010 using namespace std; struct CNode { int L,R; //[L,R] CNode *pLeft,*pRight; long long int nSum; long long int inc; }Tree[MAXN]; int n,m,ql,qr,nCount=0; void BuildTree(CNode *pRoot,int L,int R) //建立节点pRoot,其区间为[L,R] { pRoot->L=L; pRoot->R=R; pRoot->nSum=0; pRoot->inc=0; if(L==R) return; //leaf node nCount++; pRoot->pLeft=Tree+nCount; nCount++; pRoot->pRight=Tree+nCount; BuildTree(pRoot->pLeft,L,(L+R)/2); BuildTree(pRoot->pRight,(L+R)/2+1,R); } void Insert(CNode *pRoot,int i,int v) //对位置i的节点初始化值为v { int mid=(pRoot->L+pRoot->R)/2; if(pRoot->L==i&&pRoot->R==i) //该节点对应区间就是位置i { pRoot->nSum=v; return; } pRoot->nSum+=v; if(i<=mid) Insert(pRoot->pLeft,i,v); //i在区间终点左边,向左子树递归 else Insert(pRoot->pRight,i,v); //否则向右子树递归 } void Add(CNode *pRoot,int a,int b,long long int c) //对节点pRoot的区间[L,R]部分增加增量 { int mid=(pRoot->L+pRoot->R)/2; if(pRoot->L==a&&pRoot->R==b) //正好就是这个区间 { pRoot->inc+=c; return; } pRoot->nSum+=c*(b-a+1); //该节点的区间和加上增量c的累积 if(b<=mid) //增加区间完全在左子树区间中 Add(pRoot->pLeft,a,b,c); else if(a>=mid+1) //增加区间完全在右子树区间中 Add(pRoot->pRight,a,b,c); else //否则,这个增加区间跨越左右子树区间,将增加区间分开处理 { Add(pRoot->pLeft,a,mid,c); Add(pRoot->pRight,mid+1,b,c); } } long long int QuerySum(CNode *pRoot,int a,int b) //查询区间和 { int mid=(pRoot->L+pRoot->R)/2; if(pRoot->L==a&&pRoot->R==b) //该节点的区间正好就是查询区间,返回这个节点的区间和,要考虑上增量inc return pRoot->nSum+(pRoot->R-pRoot->L+1)*pRoot->inc; pRoot->nSum+=(pRoot->R-pRoot->L+1)*pRoot->inc; //先加上增量 Add(pRoot->pLeft,pRoot->L,mid,pRoot->inc); //将增量带到子树下 Add(pRoot->pRight,mid+1,pRoot->R,pRoot->inc); pRoot->inc=0; //清空增量 if(b<=mid) return QuerySum(pRoot->pLeft,a,b);//整个查询区间在左子树区间 else if(a>=mid+1) return QuerySum(pRoot->pRight,a,b); //整个查询区间在右子树区间 else //否则查询区间跨越左右子树区间,对查询区间分开处理 { return QuerySum(pRoot->pLeft,a,mid)+QuerySum(pRoot->pRight,mid+1,b); } } int main() { scanf("%d%d",&n,&m); BuildTree(Tree,1,n); //先建立空的树 for(int i=1;i<=n;i++) { int a; scanf("%d",&a); Insert(Tree,i,a); //对位置i的叶节点更新其值为a } for(int i=1;i<=m;i++) { int a,b,c; char request[4]={0}; scanf("%s",request); if(request[0]=='Q') //区间求和 { scanf("%d%d",&a,&b); printf("%lld\n",QuerySum(Tree,a,b)); } else //区间增加 { scanf("%d%d%d",&a,&b,&c); Add(Tree,a,b,c); } } return 0; }2、POJ 2528 Mayor's posters(离散化线段树)
http://poj.org/problem?id=2528
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 1000 using namespace std; struct CPost { int L,R; //海报左右点坐标 }posters[10100]; //海报信息 int x[20200]; //海报的端点瓷砖编号 int hash[10000010]; //hash[i]=瓷砖i所处离散化后的区间编号 struct CNode { int L,R; bool bCovered; //表示本区间是否被报纸盖住 CNode *pLeft,*pRight; //左右子树指针 }Tree[1000000]; int nCount=0,n; void BuildTree(CNode *pRoot,int L,int R) //建立节点pRoot,其区间为[L,R] { pRoot->L=L; pRoot->R=R; pRoot->bCovered=false; //比一般的线段树多处理一点 if(L==R) return; //这是叶节点,不需要递归 nCount++; pRoot->pLeft=Tree+nCount; nCount++; pRoot->pRight=Tree+nCount; BuildTree(pRoot->pLeft,L,(L+R)/2); BuildTree(pRoot->pRight,(L+R)/2+1,R); } bool Post(CNode *pRoot,int L,int R) //有点不同的线段树更新,插入一张覆盖区间[L,R]的海报,返回true则说明这张海报是部分或全部可见的 { int mid=(pRoot->L+pRoot->R)/2; if(pRoot->bCovered) return false; if(pRoot->L==L&&pRoot->R==R) //这个节点对应区间就是这张海报对应的区间 { pRoot->bCovered=true; return true; } bool bResult; if(R<=mid) //海报区间完全在左子树 bResult=Post(pRoot->pLeft,L,R); else if(L>=mid+1) //海报区间完全在右子树 bResult=Post(pRoot->pRight,L,R); else //否则,海报区间跨越左右子树区间 { bool b1=Post(pRoot->pLeft,L,mid); bool b2=Post(pRoot->pRight,mid+1,R); bResult=b1||b2; //最终该海报是否被覆盖了,要取决于左右两半的是否覆盖情况 } //最后要更新根节点的覆盖情况,它取决于左右子树区间对应的覆盖情况 if(pRoot->pLeft->bCovered&&pRoot->pRight->bCovered) pRoot->bCovered=true; return bResult; } int main() { int TestCase; scanf("%d",&TestCase); for(int Case=1;Case<=TestCase;Case++) { scanf("%d",&n); int tot=0; for(int i=0;i<n;i++) { scanf("%d%d",&posters[i].L,&posters[i].R); x[tot++]=posters[i].L; //新加入一个海报端点 x[tot++]=posters[i].R; } sort(x,x+tot); tot=unique(x,x+tot)-x; //去掉重复的海报端点 //对线段区间离散化 int nIntervalNo=0; //线段树区间长度 for(int i=0;i<tot;i++) { hash[x[i]]=nIntervalNo; //给这个端点一个编号 if(i<tot-1) { if(x[i+1]-x[i]==1) //两个坐标之间是相邻的 nIntervalNo++; else //两个坐标之间不相邻 nIntervalNo+=2; } } BuildTree(Tree,0,nIntervalNo); int nSum=0; //答案 for(int i=n-1;i>=0;i--) //从上到下反顺序插入海报 if(Post(Tree,hash[posters[i].L],hash[posters[i].R])) nSum++; printf("%d\n",nSum); } return 0; }
1、POJ 1182 食物链
http://poj.org/problem?id=1182
题目是中文的,就不说题目大意了。。。
这个题就是并查集加偏移量,每个节点不仅要保存它的父亲,还要保存它所属的类型。此题大致思路就是:每次输入的话都当成它是正确的,如果两个动物之间的关系不确定,就把它们的关系建立起来(并查集合并),否则检查它们的关系是否和输入的话吻合,如果不吻合,则这句话就当成是假话。
每个节点所属动物类型是不固定的,而并查集合并时也没有必要把集合中所有的元素都更新,我们只需要在每次并查集查找时,更新查找路径上的节点即可,因为只有这些节点是我们需要的。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 51000 //动物个数最大值 #define SAME 0 //自己人 #define ENEMY 1 //敌人 #define FOOD 2 //自己的食物 using namespace std; struct Node { int father; //父亲 int num; //编号 int relation; //属于何种动物 }animal[MAXN]; long long int ans; int n,k; int findSet(Node *node) //并查集查找 { int tmp; if(node->father==node->num) //并查集根结点 return node->father; tmp=node->father; //暂时保存下node的父亲 node->father=findSet(&animal[node->father]); node->relation=(animal[tmp].relation+node->relation)%3; //更新本结点所属动物编号 return node->father; } void Union(int x,int y,int a,int b,int d) //x的祖先是a,y的祖先是b { animal[b].father=a; animal[b].relation=((3-animal[y].relation)+d-1+animal[x].relation)%3; } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) { animal[i].num=i; animal[i].father=i; animal[i].relation=SAME; } for(int i=0;i<k;i++) //k次声明 { int d,x,y; scanf("%d%d%d",&d,&x,&y); //动物x和动物y之间关系为d if(x>n||y>n) ans++; //Case 1:x或y的编号比n大,假话 else { if(d==2&&x==y) //Case 2:x吃y但是x和y是同类,假话 ans++; else { int rootx=findSet(&animal[x]); int rooty=findSet(&animal[y]); if(rootx!=rooty) //查找后发现目前x和y还不是同类 { Union(x,y,rootx,rooty,d); } else //查找后发现x和y在同一集合内,即x和y的关系已经确定 { switch(d) { case 1: //x和y是同类 if(animal[x].relation!=animal[y].relation) //实际上x和y不是同类,假话 ans++; break; case 2: //x吃y if(((animal[y].relation+3-animal[x].relation)%3)!=1) ans++; break; } } } } } printf("%lld\n",ans); return 0; }2、POJ 1988 Cube Stacking
http://poj.org/problem?id=1988
题目大意:有一堆方块,初始时它们都堆在地上,每次输入有两种:1、操作,将a所属的堆移到b所属堆上,2、查询,询问x方块下面的方块个数
这个题需要用并查集来模拟方块的合并操作过程,如下图,我们需要保证每个集合(堆)的根节点就是堆底部的方块,这样合并操作时,我们可以用并查集合并的方式来模拟操作。
每次合并操作时需要更新堆底方块的sum和under,其中sum代表这个方块归属的堆的元素个数,under代表这个方块下面的方块个数。
同上题类似,这个题在并查集查找时也需要更新查找路径上的结点信息
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 31000 using namespace std; int f[MAXN]; //节点的父亲 int sum[MAXN]; //节点所在堆中方块的总个数 int under[MAXN]; //这个节点对应方块下面有多少方块 int findSet(int x) { if(f[x]==x) return x; int tmp=findSet(f[x]); //暂时将x的根节点保存起来,先不要将f[x]赋值为tmp under[x]+=under[f[x]]; //a下面的方块要加上它父亲下面的方块 return f[x]=tmp; } void Union(int a,int b) //把a所在的堆放到b所在的堆上 { int n,roota=findSet(a),rootb=findSet(b); if(roota==rootb) return; //两个方块所在的堆是一样的 f[rootb]=roota; under[rootb]=sum[roota]; //由于合并并查集时已经保证每个集合的根节点是一个堆的底部,所以这时这个集合根节点下面的方块个数就是sum[rooa] sum[roota]+=sum[rootb]; //a所在堆的方块个数要加上b所在堆的方块个数 } int main() { for(int i=0;i<MAXN;i++) { sum[i]=1; under[i]=0; f[i]=i; } int p; scanf("%d",&p); for(int i=1;i<=p;i++) { char cmd[4]; int x,y; scanf("%s",cmd); if(cmd[0]=='M') //移动操作 { scanf("%d%d",&x,&y); Union(y,x); } else { scanf("%d",&x); findSet(x); //先要来一次并查集查找,更新x节点的相关信息 printf("%d\n",under[x]); } } return 0; }
3、POJ 2492 A Bug's Life
http://poj.org/problem?id=2492
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 2200 #define SAME 0 #define DIFFERENT 1 using namespace std; int f[MAXN]; int relative[MAXN]; //relative[i]=SAME表示和集合根节点同性,否则异性 int n,q; int findSet(int x) { if(f[x]==x) return x; int tmp=findSet(f[x]); //先将该集合的根节点保存起来,不要马上赋值 relative[x]=(relative[x]+relative[f[x]])%2; //更新本节点的性别 f[x]=tmp; return f[x]; } int Union(int a,int b) { int roota=findSet(a),rootb=findSet(b); if(roota==rootb) //已经合并过了 { if(relative[a]==relative[b]) //两个虫子性别一样 return 1; //gay return 0; //异性恋 } f[rootb]=roota; relative[rootb]=(relative[a]-relative[b]+1)%2; return 0; //异性恋 } int main() { int TestCase; scanf("%d",&TestCase); for(int Case=1;Case<=TestCase;Case++) { for(int i=0;i<MAXN;i++) { f[i]=i; relative[i]=0; } bool isHomoSexual=false; scanf("%d%d",&n,&q); for(int i=1;i<=q;i++) { int x,y; scanf("%d%d",&x,&y); //x和y交配 if(Union(x,y)) isHomoSexual=true; } if(isHomoSexual) printf("Scenario #%d:\nSuspicious bugs found!\n\n",Case); else printf("Scenario #%d:\nNo suspicious bugs found!\n\n",Case); } return 0; }
4、POJ 1456 Supermarket
http://poj.org/problem?id=1456
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 10010 using namespace std; struct Product { int p,d; //截止日期、价值 }product[MAXN]; int f[MAXN],n; int findSet(int x) { if(f[x]==x) return x; return f[x]=findSet(f[x]); } void Union(int a,int b) { f[a]=b; } bool cmp(Product a,Product b) { return a.p>b.p; } int main() { while(scanf("%d",&n)!=EOF) { int ans=0; for(int i=0;i<MAXN;i++) f[i]=i; //并查集初始化 for(int i=1;i<=n;i++) scanf("%d%d",&product[i].p,&product[i].d); //输入 sort(product+1,product+n+1,cmp); //根据价值降序排序 for(int i=1;i<=n;i++) { int rootx=findSet(product[i].d); if(rootx>0) //剩下的空余时间段中可以购买第i号商品 { ans+=product[i].p; f[rootx]=rootx-1; } } printf("%d\n",ans); } return 0; }
5、POJ 2236 Wireless Network
http://poj.org/problem?id=2236
题目大意:现在坐标系里有n台坏掉的电脑,给出每台电脑的坐标,两台电脑之间的最大通信距离为d,给出多组操作,第一种操作是修好第i号电脑,第二种操作是检查第i号和第j号电脑之间能否通信,要求出每次第二种操作的答案(可以通信或不可以通信)
思路:将每个电脑看做并查集中的节点,并查集中的一个集合中的所有电脑都是好的,且均能互相通信(两两距离小于d),对于每次修电脑的操作,我们将这台电脑、与这台电脑距离小于d且修好的电脑合并在一起,这样每次询问操作就是并查集的查询操作了
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 1100 using namespace std; bool hasRepaired[MAXN]; int n,d; struct Node { int father; //父节点 int x,y; //该电脑坐标 }node[MAXN]; int findSet(int x) //并查集查找 { if(node[x].father==x) return x; return node[x].father=findSet(node[x].father); } void Union(Node a,Node b) { int roota=findSet(a.father); int rootb=findSet(b.father); if(roota!=rootb) //a、b所在集合尚未合并 if((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)<=d*d) //a、b之间的距离小于最大通信距离,则将他们所在子集合并 node[roota].father=rootb; } int main() { scanf("%d%d",&n,&d); for(int i=0;i<MAXN;i++) node[i].father=i; for(int i=1;i<=n;i++) scanf("%d%d",&node[i].x,&node[i].y); char cmd[4]; while(scanf("%s",cmd)!=EOF) { if(cmd[0]=='O') { int num; //num=要修的电脑编号 scanf("%d",&num); hasRepaired[num]=true; //标记这个电脑已经修过 for(int i=1;i<=n;i++) //遍历n台电脑,将这台电脑和已经修好的、且距离不远电脑合并 if(i!=num) if(hasRepaired[i]) Union(node[i],node[num]); } else { int from,to; scanf("%d%d",&from,&to); if(findSet(from)==findSet(to)) printf("SUCCESS\n"); else printf("FAIL\n"); } } return 0; }
6、POJ 1984 Navigation Nightmare
http://poj.org/problem?id=1984
题目大意:现在有n个点,m个提示(a,b,dir,len),表示点b在点a的dir方向(东西南北),两点之间距离为len,并给出q组询问(a,b,index),要求利用前index个提示能否确定点a和点b的关系,能确定的话,求出两点之间的曼哈顿距离。
思路:
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #include <map> #include <cmath> #define MAXN 10005 using namespace std; struct Node { int x,y,father; //点的坐标为(x,y),其父亲为father }node[4*MAXN]; //保存并查集的节点 struct Edge { int t1,t2,dir,len; //t1和t2之间存在相对位置关系,t1与t2距离为len,t1到t2的方向为dir }edges[4*MAXN]; struct Query { int t1,t2,num,id; //查询 }query[MAXN]; map<char,int>CharOfDir; int xx[]={1,-1,0,0},yy[]={0,0,1,-1}; int n,m; int ans[MAXN]; //保存询问结果 Node findSet(int x) { if(node[x].father==x) return node[x]; Node tmp=findSet(node[x].father); node[x].x+=tmp.x; //更新本节点的对应坐标 node[x].y+=tmp.y; node[x].father=tmp.father; return node[x]; } bool cmp(Query a,Query b) { if(a.num!=b.num) return a.num<b.num; return a.id<b.id; } int main() { CharOfDir['E']=0; //对于方向的定义没有多大要求,只需要注意相反方向移动坐标的方向也是对应相反的 CharOfDir['W']=1; CharOfDir['N']=2; CharOfDir['S']=3; char director[4]; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) //并查集初始化 { node[i].x=node[i].y=0; node[i].father=i; } for(int i=0;i<m;i++) { scanf("%d%d%d%s",&edges[i].t1,&edges[i].t2,&edges[i].len,director); edges[i].dir=CharOfDir[director[0]]; } int q; scanf("%d",&q); for(int i=0;i<q;i++) { scanf("%d%d%d",&query[i].t1,&query[i].t2,&query[i].num); query[i].id=i; } sort(query,query+q,cmp); //对询问排序 int j=0; for(int i=0;i<q;i++) { while(j+1<=query[i].num) { int x=edges[j].t1,y=edges[j].t2; //一条边j的两端点为t1、t2 Node rootx=findSet(x),rooty=findSet(y); if(rootx.father!=rooty.father) //两个点之间没有合并 { node[rooty.father].x=rootx.x-rooty.x+xx[edges[j].dir]*edges[j].len; //更新t2的父亲的点的横坐标 node[rooty.father].y=rootx.y-rooty.y+yy[edges[j].dir]*edges[j].len; //更新t2的父亲的点的纵坐标 node[rooty.father].father=rootx.father; //t2所属的集合并到t1所属集合根结点下 } j++; } Node x=findSet(query[i].t1),y=findSet(query[i].t2); //回答第i个问题,求xi与yi之间的曼哈顿距离 if(x.father!=y.father) //x和y的相对位置关系还不确定 ans[query[i].id]=-1; else ans[query[i].id]=abs(x.x-y.x)+abs(x.y-y.y); } for(int i=0;i<q;i++) printf("%d\n",ans[i]); return 0; }
1、POJ 1195 Mobile Phone(裸二维树状数组)
http://poj.org/problem?id=1195
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #define MAXN 1100 using namespace std; int C[MAXN][MAXN]; //矩阵C int lowbit[MAXN]; //lowbit数组保存打表结果 int s; void update(int y,int x,int val) //二维的点修改,让点(x,y)增加val { while(y<=s) { int tmpx=x; while(tmpx<=s) { C[y][tmpx]+=val; tmpx+=lowbit[tmpx]; } y+=lowbit[y]; } } int query(int y,int x) //查询左上角点(1,1),右下角点(x,y)构成的子矩阵的和 { int ans=0; while(y>0) { int tmpx=x; while(tmpx>0) { ans+=C[y][tmpx]; tmpx-=lowbit[tmpx]; } y-=lowbit[y]; } return ans; } int main() { for(int i=1;i<MAXN;i++) lowbit[i]=i&(-i); while(1) { int cmd; scanf("%d",&cmd); switch(cmd) { case 0: //0:重置矩阵 { scanf("%d",&s); memset(C,0,sizeof(C)); //重置矩阵 break; } case 1: //1:将矩阵中一点增加值a { int x,y,a; scanf("%d%d%d",&x,&y,&a); update(x+1,y+1,a); break; } case 2: //2:查询左上角点(L,B),右下角点(R,T)所构成子矩阵元素和 { int l,b,r,t; scanf("%d%d%d%d",&l,&b,&r,&t); l++,b++,r++,t++; int sum1=query(l-1,b-1),sum2=query(r,t),sum3=query(l-1,t),sum4=query(r,b-1); printf("%d\n",sum2-sum3-sum4+sum1); break; } case 3: //3:结束程序 return 0; } } return 0; }