CF56E 【Domino Principle】
$ $

题目翻译:

\(n\) ( \(n \le 10^5\) ) 个多米诺骨牌在一条直线上,给定他们的坐标 \(x\)和高度 \(h\) ( \(x\) 越大则越靠右) ,求出当第 \(i\) 个骨牌向右倒下时会有几个骨牌倒下。第 \(i\) 块骨牌能压倒的范围在 \([x_i+1,x_i+h_i-1]\) 之间。

需要注意的地方:

  • 输入中骨牌的 \(x\) 值不一定有序
  • 被压倒骨牌可能也会压倒其他骨牌

思路:

直接暴力算肯定会T,二分的话因为会有重复部分所以也不好做,因此考虑动态规划。

  1. 先按照 \(x\) 坐标排正序,但枚举时要从后向前枚举 (因为后面骨牌的不会影响前面,因此先从后面算
  2. 每次计算 \(ans_i\) 时先考虑 \(i\) 能否压倒 \(i+1\),如果可以就加上 \(ans_{i+1}\) ,然后再看 \(i\) 能否压倒 \(i+1\) 不能压倒的第一块骨牌,可以答案就再加上那块骨牌的答案,以此类推。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<bitset>
#include<cmath>
#include<queue>
#include<map>
#include<set>

using namespace std;

int read()
{
	int ans=0,f=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
	return ans*f;
}

const int N=1e5+5;
int n,ans[N],last[N];
// last 存储排序后的第 i 块骨牌压不倒的第一块骨牌的位置

struct Node
{
	int x,h,p;  // p 用来标记问题编号
	bool operator < (const Node &t)const
	{  // 重载运算符,排序时按照 x 正序排列
		return t.x>x;
	}
}a[N];

int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	{
		a[i].x=read();
		a[i].h=read();
		a[i].p=i;
	}  // 读入
	sort(a+1,a+1+n); / / 排序
	for(int i=1;i<=n;++i)
		ans[i]=1; // 因为压倒一块骨牌时它自己一定会倒(这不是废话吗...),所以答案至少是 1
	for(int i=n-1;i>=1;--i)
    // 因为最后面的骨牌谁都压不到,所以直接从 n-1 开始算
	{
		int now=i+1;  // now 存储下一次需要比较的骨牌下标,最开始为 i+1
		while(now&&a[i].x+a[i].h>a[now].x)
        // 如果后面还有骨牌 并且 这块骨牌能压倒下一个需要压倒骨牌
		{
			ans[a[i].p]+=ans[a[now].p];
            // 就把答案加上下一块需要压倒的骨牌的答案
			now=last[now];
            // 并更新 now 为 刚压倒的这块骨牌 的首个无法压倒的骨牌的下标
		}
		last[i]=now;  // 维护 last ,因为下标为 now 的骨牌已经无法被压倒,且它前面的骨牌都可以被压倒,所以 now 就是这块骨牌的首个无法被压倒的骨牌
	}
	for(int i=1;i<=n;++i)
    //输出时注意按照原输入顺序来输出
		printf("%d ",ans[i]);
	return 0;
}