一、二叉树
二、树状数组
(一)逆序对问题
1、POJ 2299 Ultra-QuickSort
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;
}