↓ Skip to main content
  1. Posts/

codeforces #609 F. Frogs and mosquitoes (线段树+二分)

·1620 words·4 mins
Note: This article is available in Chinese only. 本文暂无英文版本。 View original

题目链接

题意:n 只青蛙,第 i 只位于 x[i],舌头长度为 t[i]。m 只蚊子,第 i 只蚊子所在位置为 p[i],蚊子的大小为 b[i]。

蚊子按照出现顺序输入。

一只青蛙能吃到蚊子当且仅当蚊子和青蛙在同一个位置,或者蚊子在青蛙右边并且与青蛙的距离小于等于该青蛙舌头的长度。

当多只青蛙可以吃到同一只蚊子的时候,最左边的那只青蛙来吃。

当一只青蛙吃掉一只蚊子以后,青蛙的舌头增加蚊子的大小。

当一只青蛙吃掉一只蚊子后,因为舌头变长而又能吃到其他蚊子的时候,这只青蛙会继续吃,直到当前没有蚊子可以被任何一只青蛙吃到,在这个过程之后才会飞来下一只蚊子。

思路:问题的关键在于,对于某只蚊子,我们如何找到能吃到该蚊子的青蛙中最左边的那只。

可以抽象成寻找区间 [1,r] 中,最左边的那个 >=x 的元素。

联想到一个类似的问题:对于区间 [1,n],找到最左边的 >=x 的元素。做法是线段树维护最大值,只需要每次先 query 左子树,就可以保证找到的是最左边的。

但是如果是区间 [1,r] 呢……

我们发现,区间 [1..k] (k<=r) 的最大值是随着 k 的增大而不减的(最大值肯定不减呀)。

也就是说是单调的,因此我们可以考虑二分,这很重要。

寻找区间 [1,r] 中第一个大于等于 x 的元素,利用单调性来二分做是一个很经典的做法,复杂度 (lg n)^2,注意体会。

因此具体做法是:

建树,存的是区间中 x[i]+t[i] 的最大值。

对于每一只飞来的蚊子,寻找能吃到它的最左边的青蛙的编号(具体做法是,先找到最后一个位置小于等于蚊子位置的青蛙的下标,作为青蛙的下标的上界,然后二分,每次 query 区间最大值,不断缩小区间)。

当蚊子被吃的时候记录答案,更新青蛙的各种信息(线段树要 update)。

并且由于青蛙的舌头变长了,可能吃到之前没有青蛙能吃到的蚊子,因此要把之前没有能被青蛙吃到的蚊子存进一个 multiset(因为蚊子可能落在同一个位置),然后让当前的青蛙尽可能吃这些没有被吃到的蚊子,同时更新青蛙的各种信息,并在 multiset 中删除这只蚊子。

没被吃的时候就扔进 multiset 里。

需要注意 multiset erase 的时候要 erase 一个迭代器,不然会把所有相同的都删掉。

代码实现
  1/* ***********************************************
  2Author :111qqz
  3Created Time :Wed 21 Sep 2016 12:30:16 AM CST
  4File Name :code/cf/problem/609F.cpp
  5************************************************ */
  6#include <cstdio>
  7#include <cstring>
  8#include <iostream>
  9#include <algorithm>
 10#include <vector>
 11#include <queue>
 12#include <set>
 13#include <map>
 14#include <string>
 15#include <cmath>
 16#include <cstdlib>
 17#include <ctime>
 18#define fst first
 19#define sec second
 20#define lson l,m,rt<<1
 21#define rson m+1,r,rt<<1|1
 22#define ms(a,x) memset(a,x,sizeof(a))
 23typedef long long LL;
 24#define pi pair < LL ,LL >
 25#define MP make_pair
 26using namespace std;
 27const double eps = 1E-8;
 28const int dx4[4]={1,0,0,-1};
 29const int dy4[4]={0,-1,1,0};
 30const int inf = 0x3f3f3f3f;
 31const int N=2E5+7;
 32struct node
 33{
 34    LL x,t;
 35    LL sum;
 36    int id;
 37    bool operator < (node b)const
 38    {
 39	return x<b.x;
 40    }
 41}frog[N];
 42pi mos[N];
 43int n,m;
 44int cnt;
 45LL X[N];
 46LL tree[N<<2];
 47void PushUp(int rt)
 48{
 49    tree[rt] = max(tree[rt<<1],tree[rt<<1|1]);
 50}
 51void build( int l,int r,int rt)
 52{
 53    if (l==r)
 54    {
 55	tree[rt] = frog[l].sum;
 56	return ;
 57    }
 58    int m = (l+r)>>1;
 59    build(lson);
 60    build(rson);
 61    PushUp(rt);
 62}
 63void update( int p,LL sc,int l,int r,int rt)
 64{
 65    if (l==r)
 66    {
 67	tree[rt] += sc;
 68	return;
 69    }
 70    int m = (l+r)>>1;
 71    if (p<=m) update(p,sc,lson);
 72    else update(p,sc,rson);
 73    PushUp(rt);
 74}
 75LL query(int L,int R,int l,int r,int rt)
 76{
 77    if (L<=l&&r<=R) return tree[rt];
 78    int m = (l+r)>>1;
 79    LL ret = -1;
 80    if (L<=m) ret = max(ret,query(L,R,lson));
 81    if (R>=m+1) ret = max(ret,query(L,R,rson));
 82    return ret;
 83}
 84int bin( int k)
 85{
 86    int l = 1;
 87    int r = upper_bound(X+1,X+cnt+1,k)-X;//r为X中第一个大于k的下标。
 88    r--;//此时r为最后一个小于等于k的下标。
 89    if (r==0||query(1,r,1,n,1)<k) return -1; //因为1..x的最大值是在x递增的意义夏单调的,所以可以二分。
 90    int res = -1;
 91    while (l<=r)
 92    {
 93	int mid = (l+r)/2;
 94	if (query(1,mid,1,n,1)>=k) r = mid-1,res = mid;
 95	else l = mid+1;
 96    }
 97    return res;
 98}
 99pi ans[N];
100int main()
101{
102	#ifndef  ONLINE_JUDGE
103	freopen("code/in.txt","r",stdin);
104  #endif
105	scanf("%d%d",&n,&m);
106	cnt = 0 ;
107	for ( int i = 1 ;i  <= n ; i++)
108	{
109	    scanf("%lld%lld",&frog[i].x,&frog[i].t);
110	    frog[i].id = i ;
111	}
112	sort(frog+1,frog+n+1);
113	for ( int i = 1 ; i <= n ; i++) frog[i].sum = frog[i].x + frog[i].t,X[++cnt] = frog[i].x;
114	build(1,n,1);
115	multiset<pi>mse;
116	for ( int i = 1 ; i <= m ; i++) scanf("%lld%lld",&mos[i].fst,&mos[i].sec);
117	for ( int i = 1 ; i <= m ; i++)
118	{
119	    int pos = bin(mos[i].fst); //找到一个最左边的i,满足X[i]<=mos[i].fst并且x[i]+t[i]>=mos[i].fst
120	    if (pos!=-1)
121	    {
122		ans[frog[pos].id].fst++;
123		frog[pos].t+=mos[i].sec;
124		update(pos,mos[i].sec,1,n,1);
125		frog[pos].sum = frog[pos].t + frog[pos].x;
126		while (!mse.empty())
127		{
128		    auto it = mse.lower_bound(make_pair(frog[pos].x,0)); //返回第一个大于等于当前青蛙位置的蚊子
129		    if (it==mse.end()||frog[pos].sum< it->fst) break; //没有这样的蚊子或者当前青蛙没办法吃到所有没被吃的蚊子中最左边(最容易被吃到)的
130		    //不然就一直吃。。。把之前没被吃的尽量吃。。。直到某个吃不到。。。
131		    frog[pos].t += it->sec;
132		    frog[pos].sum += it->sec;
133		    ans[frog[pos].id].fst++;
134		    update(pos,it->sec,1,n,1);
135		    mse.erase(it);//论multiset的正确erase方法
136		}
137	    }else mse.insert(mos[i]);
138	}
139	for ( int i = 1 ; i <= n ; i++) ans[frog[i].id].sec = frog[i].t;
140	for ( int i = 1; i <= n ; i++) printf("%lld %lld\n",ans[i].fst,ans[i].sec);
141  #ifndef ONLINE_JUDGE
142  fclose(stdin);
143  #endif
144    return 0;
145}

Related

codeforces 540 E. Infinite Inversions (分类思想+线段树求逆序对)

·1442 words·3 mins
题目链接 题意:一个无穷数列,从1开始,初始第i个位置上为i,给出n个swap,每次交换两个位置的数。问交换 n 次以后得到的数列中,逆序对的个数。 思路: 官方题解: At first find the position of each element which is used in swap (using map). Now let’s find the answer. It consists of the two parts. First part is the number of inversions formed by only whose elements which took part in the swaps. They can be counted by one of the standard ways: mergesort or Fenwick tree. The second part is the number of inversions formed by pairs of elements where one element has been swapped even once, and the other element stayed at his position. Let’s consider the following test:

poj 3579 Median (尺取法+二分)

·499 words·1 min
题意:给出n个数,两两做差的绝对值,共有m=n*(n-1)/2个,问其中的中位数是多少。特别地,当m为偶数的时候,中位数为第m/2个。 思路:二分中位数。 一开始还觉得由于中位数在整数意义上不连续不能二分。。。。

poj 2456 Aggressive cows (二分)

·347 words·1 min
题目链接 题意:给出n个x轴上的坐标点,选取其中c个,问c个之中任意两个点的最小距离最大是多少。 思路:二分距离check合法性。 大水题。。。因为想把三分艹掉。。。三分的题又多和二分挂在一起。。。顺便就写了。。。。

codeforces 515 E. Drazil and Park ( 线段树区间合并)

·886 words·2 mins
题目链接 题意:圆上,询问任意一段弧中,任意两点的距离+两点的权值和的最大值。 思路: 1.环先拆成串,复制 1..n 到后面,变成 1..2n。 化简公式: 2 * h[u] + 2 * h[v] + dist(u, v) = 2 * h[v] + d[1] + d[2] + … + d[v-1] + 2 * h[u] - (d[1] + d[2] + … + d[u-1]).

codeforces #351 D. Jeff and Removing Periods (线段树/树状数组判断位置成等差数列)

·1339 words·3 mins
题目链接 题意:有 n 个数,每次可以删除掉数值相同并且所在位置成等差数列(只删 2 个数或者只删 1 个数应该也是可以的),删掉这些数以后可以将剩下的数重新以任意顺序排列,称为一次操作。现在给出 m 个询问,每个询问一个区间 [l,r],问删光区间 [l,r] 中的数最少需要的操作次数。