1. 程式人生 > 實用技巧 >P3572 [POI2014]PTA-Little Bird

P3572 [POI2014]PTA-Little Bird

Link

題意

\(n\) 棵樹,第 \(i\) 棵樹的高度是 \(d_i\)

\(\texttt{HLY}\) 要去第 \(n\) 棵樹。

如果 \(\texttt{HLY}\) 在第 \(i\) 棵樹,那麼他可以跳到第 \(i+1,i+2,\cdots,i+k\) 棵樹。

如果她跳到一棵不矮於當前樹的樹,那麼他的勞累值會 \(+1\);否則不會。

\(\texttt{HYL}\) 到達第 \(n\) 棵樹的最小勞累值。


考試的時候想到了可以用單調佇列來優化,結果卻 yy 出了一種錯誤的想法,維護最大值和次大值。

結果,最後只能交了 \(n^2\) 的暴力滾粗。

首先, \(n^2\)

的暴力dp,肯定都比較容易想到。

\(f[i] = min(f[i], f[j] + (h[i] >= h[j]) ) 其中 j \in {1,i-k}\)

這個就可以聯想到單調佇列優化,因為是求 \(i-k,i\) 的區間最小值。

但這個高度就處理起來很麻煩。

我們考慮什麼時候是不優的。

  • \(f[q[tail]] < f[i]\) ,也就是說 \(f[q[tail]] >= f[i] + 1\) ,我們直接可以把 \(q[tail]\) 出隊。
  • 就算 \(f[q[tail] + 1\) 也不會比 \(f[i]\) 更優。
  • \(f[q[tail]] = f[i]\)
    這時候就要仔細想想了,如果 \(f\) 陣列的值相同的話,我們要儘可能的讓高度大的留下來,因為這樣後面轉移時可以承接更多的樹,也是就會
  • 使加一的次數變小很多。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,t,k,head,tail,h[1000010],f[1000010],q[1000010];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
	return s * w;
}
int main()
{
//	freopen("t4.in","r",stdin);
//	freopen("t4.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; i++) h[i] = read();
	t = read();
	while(t--)
	{
		k = read();
		memset(f,0x3f,sizeof(f));
		head = 1,tail = 0; 
		f[1] = 0; q[++tail] = 1;//先把一號點入隊
		for(int i = 2; i <= n; i++)
		{
			while(head <= tail && q[head] < i-k) head++;//把過期的元素彈出
			if(h[q[head]] <= h[i]) f[i] = f[q[head]] + 1;//先更新在把這個點入隊
			else f[i] = f[q[head]];
			while(head <= tail && (f[q[tail]] > f[i] || (f[q[tail]] == f[i] && h[q[tail]] <= h[i]))) tail--;//特判一下f值相同的情況
			q[++tail] = i;
		}
		printf("%d\n",f[n]);
	}
	fclose(stdin); fclose(stdout);
	return 0;
}