luoguP1941飛揚的小鳥NOIP(細節dp)

題目鏈接

題意:

對於每一單位時間可以向上跳k*x[i],從i-1–>i,也可以向下掉y[i]
很明顯的dp思路卻有許多細節要處理
設dp[i][j]表示到(i,j)這一位置的最少點擊次數,根據題意可以推出轉移方程:dp[i][j]=min(dp[i-1][j-k*x[i-1]],dp[i-1][j+y[i-1]])
就這樣轉移稍微考慮一下邊界就行了嗎?那就WA聲一片了

三大細節:

1.按照上面的遞推式複雜度O(nmk),考慮k的最壞情況是m,那麼顯然超時,應該七十分左右
那麼考慮優化,觀察 dp[i][j],dp[i][j-x[i]-1],dp[i][j-2*x[i-1]]…
我們或許會發現一下規律
以x[i]=3爲例:
dp[3,10]= min{dp[2,7]+1, dp[2,4]+2, dp[2,1]+3 }
dp[3,7]= min{dp[2,4]+1, dp[2,1]+2 }
f[3,4]= min{f[2,1]+1}
可以發現對於點多次屏幕的情況可以從f[i][j-x[i-1]]直接遞推出來,不需要枚舉k(dp[3,10]中的dp[2,4]和dp[2,1]都是存在於dp[3][7]中的唯一不同的就是加了1),於是方程可以寫成:
dp[i][j]=min(dp[i1][jx[i1]]+1,dp[i][jx[i1]]+1)dp[i][j]=min(dp[i-1][j-x[i-1]]+1,dp[i][j-x[i-1]]+1)
可以O(NM)求出

2.注意讀題可以發現到了m就無法走了,其實這個很好處理,在處理上面那一步時將j的範圍擴大到m+x[i-1],完成之後再用上面一步所求出的溢出的dp值更新dp[i][m]即可

3.注意把有管子的部分設爲正無窮

下面上代碼
由於x,y都是從1開始讀,所以下面x[i],y[i]與上面x[i-1],y[i-1]對應

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int N=1e4+10,M=2e3+10;
int n,m,k,dp[N][M],x[N],y[N],low[N],high[N],flag[N];
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]);
	for(int i=1;i<=n;i++) low[i]=0,high[i]=m+1;
	for(int i=1;i<=k;i++){
		int p,l,h; scanf("%d%d%d",&p,&l,&h);
		low[p]=l,high[p]=h,flag[p]=1;
	}
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=m;i++) dp[0][i]=0;
	for(int i=1;i<=n;i++){
		for(int j=1+x[i];j<=m+x[i];j++) dp[i][j]=min(dp[i-1][j-x[i]]+1,dp[i][j-x[i]]+1);//步驟1
		for(int j=m+1;j<=m+x[i];j++) dp[i][m]=min(dp[i][m],dp[i][j]);//步驟2
		for(int j=1;j<=m-y[i];j++) dp[i][j]=min(dp[i][j],dp[i-1][j+y[i]]);//考慮向下
		for(int j=1;j<=low[i];j++) dp[i][j]=INF;
		for(int j=high[i];j<=m;j++) dp[i][j]=INF;
		//不合法狀態置爲INF 
	}
	int ans=INF;
	for(int i=1;i<=m;i++) ans=min(ans,dp[n][i]);
	if(ans!=INF){
		printf("%d\n%d\n",1,ans);
		return 0;
	}
	ans=0;
	for(int i=1;i<=n;i++){
		int ok=0;
		for(int j=1;j<=m;j++) if(dp[i][j]<INF){ok=1;break;} 
		if(!ok) break;
		if(flag[i]) ans++;
	}
	printf("%d\n%d\n",0,ans);
} 

謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章