1. 程式人生 > 實用技巧 >Luogu - CF56E 【Domino Principle】 - 題解

Luogu - CF56E 【Domino Principle】 - 題解

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