[NOIP 2014复习]第六章:数据结构

一、二叉树

二、树状数组

(一)逆序对问题

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

[NOIP 2014复习]第六章:数据结构_第1张图片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]

[NOIP 2014复习]第六章:数据结构_第2张图片

#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代表这个方块下面的方块个数。

同上题类似,这个题在并查集查找时也需要更新查找路径上的结点信息

[NOIP 2014复习]第六章:数据结构_第3张图片

#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


[NOIP 2014复习]第六章:数据结构_第4张图片

#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;
}


六、二维线段树(树套树)








你可能感兴趣的:([NOIP 2014复习]第六章:数据结构)