1. 程式人生 > 其它 >小歐米伽的仙人掌

小歐米伽的仙人掌

小歐米伽的仙人掌

題面:


​ 對於一段區間 \([l,r]\) 如果 \([l,r]\) 不滿足題目所給條件,那麼 \([l+1,r]\) 一定不滿足題目所給條件。那麼我們容易想到用雙指標來解決這個問題。

​ 但是我們發現,當左指標向右移動時,我們不好直接減去左指標所在位置的貢獻。這裡有一種常見的處理方法。我們維護兩個棧。假設當前左端點為 \(l\),右端點為 \(r\)\(mid\)\([l,r]\) 中任意一個點,然後一個棧自棧頂到棧底維護的是 \([i,mid],i\in[l,mid]\) 這個區間的 dp 值。另一個棧自棧頂到棧底維護的是 \([mid+1,i],i\in[mid+1,r]\)

這個區間的 dp 值,那麼右指標移動就相當於用往右邊那個棧壓入一個新的 dp​ 值,左指標移動就相當於左邊那個棧彈出一個 dp 值。當左邊那個棧空了的時候,我們就將右邊那個棧清空,然後將 \(mid\) 設為 \(r\) ,然後我們暴力計算 \([i,mid],i\in[l,mid]\) 這些區間的 dp 值,並且依次壓入左邊的棧中。

​ 查詢的時候我們只要合併兩邊棧頂的 dp 值就行,複雜度為 \(O(m)\)

​ 然後每個位置的元素分別會被左邊那個棧和右邊那個棧壓入和彈出一次,每壓入一次的複雜度是 \(O(m)\)

​ 總時間複雜度為 \(O(m\times n)\)

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e4+5;
int n,W,K,A[MAXN],B[MAXN],ans,f[MAXN][5005],l,r;
bool check(int x)
{
	if(r==l) return f[x][W]<=K;
	for(int i=0;i<=W;++i)
		if(f[x][i]+f[r][W-i]<=K) return true;
	return false;
}
int main()
{
	freopen("cactus.in","r",stdin);
	freopen("cactus.out","w",stdout);
	scanf("%d %d %d",&n,&W,&K);
	for(int i=1;i<=n;++i) scanf("%d %d",&A[i],&B[i]);
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;++i) f[i][0]=0;
	l=1,r=1;ans=n+1;
	f[1][A[1]]=B[1];
	for(int i=1;i<=n;++i)
	{
		while(!check(i)&&r+1<=n)
		{
			++r;
			if(r==l+1) f[r][A[r]]=B[r];
			else
			{
				for(int j=W;j>=0;--j)
				{
					f[r][j]=f[r-1][j];
					if(j>=A[r]) f[r][j]=min(f[r][j],f[r-1][j-A[r]]+B[r]);
				}
			}
		}
		if(check(i)) ans=min(ans,r-i+1);
		if(i==l)
		{
			memset(f[r],0x3f,sizeof f[r]);
			f[r][0]=0;f[r][A[r]]=B[r];
			for(int j=r-1;j>l;--j)
			{
				for(int k=W;k>=0;--k)
				{
					f[j][k]=f[j+1][k];
					if(k>=A[j]) f[j][k]=min(f[j+1][k-A[j]]+B[j],f[j][k]);
				}
			}
			l=r;
		}
	}
	printf("%d\n",ans>n?-1:ans);
	return 0;
}

路漫漫其修遠兮,吾將上下而求索。