P1941 飞扬的小鸟

知识点: 完全背包,01背包

原题面

分析题意:

\(f[i][j]\) 表示横坐标为 \(i\) , 纵坐标为 \(j\) 时的 最少花费

先初始化 \(f[i][0] = INF \text(不可落地)\) ,
\(f[0][i]=0 \text(可以选择横坐标为0的任意一处为起点)\)

可以发现, 对于一个确定的横纵坐标的位置:

  1. 由 横坐标\(-1\) 处不上升,下落得到
  2. 由 横坐标\(-1\) 处经过 多次上升操作得到

那么可以简单的 得到状态转移方程式:

  1. \(j<m\) 时 ,有: \(f[i][j] = min(f[i][j] , f[i-1][j - k\times x[i]] + k) (k \in Z)\)
  2. \(j=m\) 时 , 已经到达了 平面的顶部,
    不可继续上升,但是可以通过任意纵坐标处 的 多次上升来得到
    则有: \(f[i][m] = min(f[i][m] , f[i-1][j + k\times x[i]]\ (j + k\times x[i]>=m\ ,\ j + k\times x[i]<m+x[i])\)
  3. 当横坐标为 \(i\) 时,如果有柱子,
    则有: \(f[i][j] = INF (\ j \in [\ 1,l[i]\ ] \cup [\ h[i],m\ ]\ )\)
    表示柱子覆盖位置 不可到达

对于每个横坐标 , 完成转移后,
都记录到达此横坐标的最小步数
如果 \(\text{最小步数} = INF\) , 则此横坐标不可到达
输出经过的柱子数即可

总复杂度为 \(O(n^2 \times m)\)
可以拿到 \(80\) 分的好成绩 (大雾


考虑优化:

对于两种位置的转移,

  1. 由 横坐标 \(-1\) 处不上升 , 下落得到 ---> 类似 \(01\) 背包
  2. 由 横坐标 \(-1\) 处经过 多次上升操作得到 ---> 类似完全背包

那么可以类比两种背包的写法 ,
来对状态转移进行优化

主要优化 第二种转移 , 类比完全背包 ,
新的转移方程式为:
\(f[i][j] = min(f[i][j],\ f[i][j-x[i-1]]+1,\ f[i-1][j-x[i-1]]+1)\)
即: 可以根据 \(\ \text{横坐标为} i-1\)\(\ \text{横坐标为} i\) 两处转移而来

对于每一个横坐标 , 先跑完全背包 , 再跑 \(01\) 背包
记录最小步数即可

总复杂度 \(O(n\times m)\)


其他优化:

观察状态转移方程式,
还可以通过滚动数组,
来滚掉第 \(1\) 维 , 以优化空间复杂度


#include<cstdio>
#include<ctype.h>
#define ll long long
#define min1(a,b) ((a)<(b)?(a):(b))
#define min(a,b,c) (min1(a,min1(b,c)))
const ll INF = 0x7fffffffff;
const int MARX = 1e4+10;
//=============================================================
int n,m,k , x[MARX],y[MARX],h[MARX],l[MARX];//输入各数据 
ll ans=INF , f[MARX][1010];//表示横坐标为i , 纵坐标为j时的 最少花费  
bool jud[MARX];//jud[i]表示 横坐标为i时是否有柱子 
//=============================================================
inline int read()
{
    int s=1, w=0; char ch=getchar();
    for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
    for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
    return s*w;
}
inline void prepare()//输入 + 初始化 
{
	n=read(),m=read(),k=read();
	for(int i=0; i<n; i++) x[i]=read(),y[i]=read();
	for(int i=1,tmp; i<=k; i++) tmp=read(),l[tmp]=read(),h[tmp]=read(),jud[tmp]=1;
	for(int i=1; i<=n; i++) f[i][0]=INF;
}
void dp()
{
	for(int i=1,num=0; i<=n; i++)
	{
	  ans = INF;
	  for(int j=1; j<=m; j++) f[i][j]=INF;//初始化极大值 
	  //由 横坐标-1 处经过 多次上升操作得到 ---> 类似完全背包  
	  for(int j=x[i-1]+1; j<m; j++) f[i][j] = min(f[i][j] , f[i-1][j-x[i-1]]+1 , f[i][j-x[i-1]]+1);
	  //纵坐标为 m的特殊情况  
	  for(int j=m-x[i-1]; j<=m; j++) f[i][m] = min(f[i][m] , f[i-1][j]+1 , f[i][j]+1);
	  //由 横坐标-1 处不上升,下落得到  --->  类似01背包 
	  for(int j=1; j+y[i-1]<=m; j++) f[i][j] = min1(f[i][j] , f[i-1][j+y[i-1]]);
	  //横坐标为i时有柱子 
	  if(jud[i])
	  {
	  	for(int j=1; j<=l[i]; j++) f[i][j]=INF;//赋极大值 
	  	for(int j=h[i]; j<=m; j++) f[i][j]=INF;
	  	num++;
	  }
	  
	  for(int j=1; j<=m; j++) ans=min1(ans,f[i][j]);//记录最小步数 
	  if(ans==INF) //此横坐标不可到达 
	  {
	  	printf("0\n%d",num-1);
	  	return ;
	  }
	}
	printf("1\n%lld",ans);
}
//=============================================================
signed main()
{
	prepare();
	dp();
}
posted @ 2019-09-07 17:40  Luckyblock  阅读(104)  评论(0)    收藏  举报