luogu P1941 [NOIP2014 提高组] 飞扬的小鸟

题目大意

给出飞扬的小鸟的一个场景,判断小鸟是否能到达终点。若能,求最少点击次数;否则,求最多能通过的水管数。

解题思路

朴素算法不难想到。
f [ i ] [ j ] f[i][j] f[i][j] 表示经过 (i, j) 的最少点击次数。
对于下降, f [ i ] [ j ] = min ⁡ ( f [ i ] [ j ] , f [ i − 1 ] [ j + y [ i ] ] ) f[i][j]=\min(f[i][j],f[i-1][j+y[i]]) f[i][j]=min(f[i][j],f[i1][j+y[i]])
对于上升, f [ i ] [ j ] = min ⁡ ( f [ i ] [ j ] , f [ i − 1 ] [ j − x [ i ] ∗ k ] + k ) , k ≥ 1 f[i][j]=\min(f[i][j],f[i-1][j-x[i]*k]+k),k≥1 f[i][j]=min(f[i][j],f[i1][jx[i]k]+k),k1
时间复杂度 O ( n m 2 ) O(nm^2) O(nm2),期望得分70。

#include <cstdio>
#include <cstdlib>
#include <cstring>

#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))

const int MAXN=10010;
const int MAXM=1010;

int n, m, k, s;
int x[MAXN], y[MAXN];
int l[MAXN], h[MAXN];
int f[MAXN][MAXM];

inline int read (){
	int x=0; char c;
	do c=getchar (); while ('0'>c||'9'<c);
	while ('0'<=c&&'9'>=c)
		x=x*10+c-48, c=getchar ();
	return x;
}
int fail (){
	int sum=0, cnt=0;
	for (int i=n; 0<=i; --i){
		for (int j=0; j<=m; ++j)
			if (f[i][j]<1e9){
				sum=i;
				break;
			}
		if (sum) break;
	}
	for (int i=0; i<=sum; ++i)
		if (-1!=l[i]&&m+1!=h[i])
			++cnt;
	return cnt;
}
int main (){
	freopen ("1.in", "r", stdin);
	n=read (); m=read (); k=read ();
	for (int i=0; i<n; ++i){
		x[i]=read (); y[i]=read ();
		l[i]=-1; h[i]=m+1;
	}
	for (int i=1; i<=k; ++i){
		s=read ();
		l[s]=read (); h[s]=read ();
	}
	memset (f, 63, sizeof (f)); l[n]=-1; h[n]=m+1;
	for (int i=0; i<=m; ++i) f[0][i]=0;
	for (int i=0; i<n; ++i)
		for (int j=l[i]+1; j<h[i]; ++j){
			if (f[i][j]>1e9) continue;
			if (j>y[i]&&l[i+1]<j-y[i]&&j-y[i]<h[i+1])
				f[i+1][j-y[i]]=min (f[i+1][j-y[i]], f[i][j]);
			for (int k=max (1, (l[i+1]+1-j)/x[i]); ; ++k){
				int ht=min (j+x[i]*k, m);
				if (l[i+1]<ht&&ht<h[i+1])
					f[i+1][ht]=min (f[i+1][ht], f[i][j]+k);
				if (ht==m||ht>=h[i+1]) break;
			}
		}
//	for (int i=m; 0<=i; --i){
//		for (int j=0; j<=n; ++j)
//			printf ("%4d", f[j][i]>1e9?-1:f[j][i]);
//		puts ("");
//	}
	int ans=0x3f3f3f3f;
	for (int i=0; i<=m; ++i)
		ans=min (ans, f[n][i]);
	if (ans<1e9)
		printf ("1\n%d", ans);
	else{
		printf ("0\n%d", fail ());
	}
}

考虑如何优化时间复杂度。
注意到算法处理上升时进行了大量冗余的更新,考虑优化它。
一个位置 f [ i ] [ j ] f[i][j] f[i][j],他能被 (i-1, j-x[i-1]) 更新,也能被 (i-1, j-x[i-2]) 更新……如果使用朴素算法更新,每个 f [ i ] [ j ] f[i][j] f[i][j] 都需要访问 m m m 个位置 j-x[i-1], j-x[i-2], …来更新。因为我们要更新 m m m 个位置 f [ i ] [ 0 ] , f [ i ] [ 1 ] , . . . , f [ i ] [ m ] f[i][0],f[i][1],...,f[i][m] f[i][0],f[i][1],...,f[i][m],所以 j-x[i-1], j-x[i-2], … 被多次访问,造成时间浪费。
优化这种 m×m 的更新方式为 “2m 型”。考虑建立中继体系(虚点)降低复杂度。
在这里插入图片描述
g [ i ] [ j ] g[i][j] g[i][j] 表示从 f [ i − 1 ] f[i-1] f[i1] 跳跃到 (i, j) 的累计最小点击数。这样 g [ i ] [ j ] g[i][j] g[i][j] 的大部分都可以只令 k = 1 k=1 k=1 完成更新。时间复杂度 O ( m ) O(m) O(m)
再用 g [ i ] [ j ] g[i][j] g[i][j] 更新 f [ i ] [ j ] f[i][j] f[i][j]。用 g [ i ] [ j − x [ i − 1 ] g[i][j-x[i-1] g[i][jx[i1] 更新 g [ i ] [ j ] g[i][j] g[i][j] 即可。时间复杂度 O ( m ) O(m) O(m)

优化后,总时间复杂度 O ( n m ) O(nm) O(nm)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值