[离散+二分]HDU4400 Mines

题意:给n个平面上的炸弹,每个炸弹有一个爆炸范围d,爆炸的时候会引爆d范围内的炸弹(注意距离为曼哈顿距离),这些炸弹可以又炸其他炸弹。然后m次询问,每次引爆编号num的炸弹,输出爆炸的次数,如果已经炸过了就输出0。

因为是平面点,我用了以y升序的mulitset,内部是有序的,就不用再去排序了,而且自带上下二分函数。STL大法好啊。

离散的过程是,x相同的就压入同一个Mset,不同的以x的升序压入Mset。

其实这题就是WA在离散上,果然基本功太渣了ORZ。

以后离散都用sort+unique+lower_bound写了,代码简单,排序复杂度是NlogN,用lower_bound插入是logN,总的复杂度是o(2NlogN)=o(NlogN)。

每次询问的时候先二分出x的左右界,然后枚举二分出y的上下界,然后已经炸了就标记。离散的时候学习了lower_bound和upper_bound,这里写的二分都用这两个函数搞了,代码容易不少。

看代码。

#include<map>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<climits>
#include<list>
#include<iomanip>
#include<stack>
#include<set>
#include<ctime>
#define pb push_back
#define pii pair<int,int>
#define LL long long int
using namespace std;
const int MN=100505;
struct node{ //记录炸弹 
    int x,y,d;
}bom[MN];
struct st{ //多重集的元素 
    int y,x;
    st(){}
    st(int a,int b){y=a,x=b;}
    bool operator<(st a)const{
        return y<a.y;
    }
};
int mak[MN]; //离散 
multiset<st>:: iterator L,R;
multiset<st> mst[MN];
bool boom[100015];c //标记是否炸了 
int main()
{
    int n,m,ca=1;
    while( scanf("%d", &n) != EOF && n )
    {
        printf("Case #%d:\n", ca++);

        memset( mak, 0, sizeof(mak) );
        memset( boom, 0, sizeof(boom) );
        for(int i = 0; i <= n +10; ++i) mst[i].clear();

        for(int i = 0; i < n; ++i){
                scanf("%d %d %d", &bom[i].x, &bom[i].y, &bom[i].d);
                mak[i]=bom[i].x;
        }
        sort(mak, mak + n);
        int cnt = unique(mak, mak + n) - mak;
        for(int i=0;i<n;++i)
		{
        	int p=lower_bound(mak, mak + cnt, bom[i].x) - mak; //效率是logN
        	mst[p].insert( st(bom[i].y, i) );
        }
        /*sort(bom, bom + n); 按x排序。 这一段是之前的离散,虽然可以AC但是效率不如用STL的高,复杂度多加一个N。
        int cnt = 1;
        mak[0] = bom[0].x;
        mst[0].insert( st(bom[0].y,bom[0].nb) );
        for(int i=1;i<n;++i)
        {
        	if(bom[i].x!=mak[cnt-1])
			{
				mak[cnt]=bom[i].x;
				mst[cnt++].insert( st(bom[i].y,bom[i].nb) );
        	}
        	else mst[cnt-1].insert( st(bom[i].y,bom[i].nb) );
        }
        sort(bom, bom + n, cmp); 还原成输入的顺序。 一定要还原成输入的顺序,因为下面询问给的序号是输入时的。
        */
        scanf("%d", &m);
        while( m-- )
        {
            int num, ans = 0;
            scanf( "%d", &num);
            num -= 1; //我们的序号是从0开始离散的
            if( boom[ num] ) { puts("0"); continue; }
            boom[ num] = 1;
            queue< int >q;
            q.push(num);
            while( !q.empty() )
            {
                int now = q.front(); q.pop(); ans += 1;
                int l = lower_bound( mak, mak + cnt, bom[ now].x - bom[now].d ) - mak; //二分出x的范围
                int r = upper_bound( mak, mak + cnt, bom[now].x + bom[now].d ) - mak; 
                for(int i = l; i < r; ++i)
                {
                    int ef = bom[ now].d - abs( bom[ now].x - mak[i] ); //算出曼哈顿距离下的范围
                    L = mst[i].lower_bound( st(bom[ now].y - ef, 0) ); //二分出y的范围
                    R = mst[i].upper_bound( st(bom[ now].y + ef, 0) );
                    for(multiset<st>:: iterator it = L; it != R; ++it)
                    {
                        if( !boom[it -> x])
                        {
                            boom[it -> x] = 1; //标记爆炸
                            q.push(it -> x); //入队看是否又引爆了其他的
                        }
                    }
                    mst[i].erase(L, R); //这一步并不是必要的,因为上面已经标记是不是炸了,而且我不清楚删除的效率如何,但在OJ上有这一步更快。
                }
            }
            printf("%d\n", ans);
        }
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值