POJ 3067 树状数组 线段树

东海岸与西海岸分别有N和M个城市,现在修高速公路连接东西海岸的城市,求路线的交点个数。

把每条高速公路记为(x,y), 即东岸的第x个城市与西岸的第y个城市修一条路。当两条路有交点时,满足(x1-x2)*(y1-y2) < 0。如果将每条路按x从小到达排序,若x相同,按y从小到大排序后:

先插入的肯定是X值小的。当插入第一条的时候,比如Y值为M(假设M=10),第二条插入的时候,Y2值为5,那么他们就会有一个交点。如果再插入第三条路线,Y3=3,那线段3和线段1,2分别会有一个交点。。。。因为我们是按照X值递增的顺序插入线段的,那么,是否可以理解为:我们插入的线段Ym    与  之前插入线段   的交点  总个数 ==之前插入线段中Y值比Ym 大   的线段的个数?因为线段Ym的X值肯定比之前插入线段的X值大,而Ym的值又比其小,所以跟之前满足条件的所有线段都有交点。在此例中即线段1和2.

怎么解决这个问题?

因为每次都有一条线段插入,插入线段后求的是比该线段的Yi大的线段的个数  即y值的个数(一个Y值可能对应多条线段,先不考虑此种情况),那么用M减Yi即可。是否能理解为区间和?用树状数组就方便了。同时,因为有线段插入,更改和也较方便。

此时再来考虑Y值对应多条线段的问题。用树状数组后,只需在插入线段的时候,将其Y值加一即可。然后每次插入一条线段,只需求区间和就可以了。

说到底其实就是求逆序数之和。

综上所述:

由于x是从小到大排序的,假设当前我们在处理第k条边,那么第1~k-1条边的x必然是小于(等于时候暂且不讨论)第k条边的 x 的,那么前k-1条边中,与第k条边相交的边的y值必然大于yk的,所以此时我们只需要求出在前k-1条边中有多少条边的y值在区间[yk, M]即可,也就是求yk的逆序数,M为西岸城市个数,即y的最大值。 所以就将问题转化成区间求和的问题,树状数组解决。当两条边的x相同时,我们记这两条边的y值分别为ya,yb(ya<yb),我们先处理(x,ya),再处理(x,yb),原因很明显,因为当x相同时,这两条边是认为没有交点的,若先处理(x,yb),那么下次处理(x,ya)时,(x,ya)就会给(x,yb)增加一个逆序,也就是将这两条边做相交处理了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
int m;
ll c[3000];
struct node
{
    int x;
    int y;
}a[1003*1002];
//因为最多有1000个城市,两两之间有路。此处要注意,不然会越界
int cmp(node a,node b)
{
    if(a.x==b.x)
        return a.y<b.y;
    else
        return a.x<b.x;
}
int lowbit(int x)
{
    return x&(-x);
}
ll sum(int x)
{
    ll ret=0;
    while(x>0)
      {
          ret+=c[x];
          x-=lowbit(x);
      }
      return ret;
}
void add(int i,int d)
{
    while(i<=m)
       {

        c[i]+=d;
        i+=lowbit(i);
       }

}
int main()
{
    int t,i,j,k,n,ncase;
    cin>>t;
    for(ncase=1;ncase<=t;ncase++)
    {
        cin>>n>>m>>k;
        memset(c,0,sizeof(c));
        ll ans=0;
        for(j=1;j<=k;j++)
        {
            scanf("%d%d",&a[j].x,&a[j].y);
        }
        sort(a+1,a+k+1,cmp);//从a[1]开始排序
        for(j=1;j<=k;j++)
        {
            add(a[j].y,1);
                  ans+=(sum(m)-sum(a[j].y));
        }
   cout<<"Test case "<<ncase<<": ";
    cout<<ans<<endl;
    }
return 0;

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值