P3143 [USACO16OPEN] Diamond Collector S

链接:这里

AC CODE


#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long

using namespace std;

int n, k, ans;
int maxm = -inf;
int a[50005], mark[50005];

int main(){
	cin >> n >> k;
	for(int i = 1; i <= n; ++i){
		scanf("%d", &a[i]);
		mark[i] = 1; 
	}
	sort(a + 1, a + n + 1);
	
	int num = 0;
	for(int i = 1, j = 1; i <= n; ++i){
		num = a[j] - a[i];
		while(j <= n && num <= k){
			mark[j + 1] = max(mark[j + 1], j - i + 1);
			j++;
			num = a[j] - a[i];
		}
		maxm = max(maxm, mark[i]);
		ans = max(ans, maxm + j - i);
	}
	
	cout << ans << endl;
	
	return 0;
}

解题报告


分析题意

本题需要找出两段最长的不重复序列,且序列最大值最小值之差不超过给定值。


初始思路

既然要寻找序列差值给定的序列,我们先排序,再用双指针找各序列的左右端点,然后记录最大值,分给maxamaxb,最后把他俩加起来。
此法可以过30%的数据。这个思路很显然没考虑到区间重叠的问题。

30% CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long

using namespace std;

int n, k;
int maxi = -inf, maxj = -inf;
int a[50005], mark[2005];

int main(){
	cin >> n >> k;
	for(int i = 1; i <= n; ++i){
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	
	int num = 0;
	for(int i = 1, j = 1; i <= n; ++i){
		num = a[j] - a[i];
		while(j <= n && num <= k){
			j++;
			num = a[j] - a[i];
		}
		if(num > k){
			int m = j - i;
			if(m > maxi)	maxi = m;
			else if(m > maxj) maxj = m;
		}
	}
	
	
	cout << maxi + maxj << endl;
	
	return 0;
}

改进1

为了解决重叠问题,我们另开一个数组mark[]用来记录第i个数可以往后延伸多长(即利用双指针,以序列的左端点i为记录点,记录序列长度),为了不重叠,再用一次双指针,分别寻找两个序列的两个左端点,最后判断是否重合,计算输出。
此方法可以过70%的数据,最后30%会TLE,原因是最后双指针寻找符合条件的序列时超时。

70% CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long

using namespace std;

int n, k;
int maxm = -inf;
int a[50005], mark[50005];

int main(){
	cin >> n >> k;
	for(int i = 1; i <= n; ++i){
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	
	int num = 0;
	for(int i = 1, j = 1; i <= n; ++i){
		num = a[j] - a[i];
		while(j <= n && num <= k){
			j++;
			num = a[j] - a[i];
		}
		int m = j - i;
		mark[i] = m;
	}
	
	for(int i = 1, j = 1; i <= n; ++i){
		if(mark[i] != 0){
			j = i;
			while( j <= n ){
				j++;
				if(i + mark[i] <= j){
					maxm = max(maxm, mark[i] + mark[j]);
				}else{
					maxm = max(maxm, j - i + mark[j]);
				}
			}
			
		}
	}
	cout << maxm << endl;
	
	return 0;
}

改进2

我们在第二次双指针寻左端点时,在找到第一个左端点时,让第二个指针直接从第一个序列的右端点的后一个元素处开始,这样的话两个序列必不可能重合,直接序列长度相加即可。
此方法可以过80%的数据,后面的20%依然TLE。

80% CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long

using namespace std;

int n, k;
int maxm = -inf;
int a[50005], mark[50005];

int main(){
	cin >> n >> k;
	for(int i = 1; i <= n; ++i){
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	
	int num = 0;
	for(int i = 1, j = 1; i <= n; ++i){
		num = a[j] - a[i];
		while(j <= n && num <= k){
			j++;
			num = a[j] - a[i];
		}
		int m = j - i;
		mark[i] = m;
	}
	
	for(int i = 1, j = 1; i <= n; ++i){
		if(mark[i] != 0){
			j = i + mark[i] - 1;
			while( j <= n ){
				j++;
				maxm = max(maxm, mark[i] + mark[j]);
			}
			
		}
	}
	cout << maxm << endl;
	
	return 0;
}

改进3

既然最后双指针寻符合序列不可取,那么有没有什么办法在之前处理a[]时就能得到答案的方法呢?
我们可以逆向思维,既然用左端点存序列最长长度不行,那能不能用右端点存序列最长长度呢?显然可以:我们依然使用双指针,前指针为i,后指针为ji每往前走,j往后走直到越界 or 超出给定值k,然后用j记录序列长度(mark[j] = max(mark[j], j - i + 1),记得取最大值),记录下每个右端点的数据,之后当i走到这个点时,mark[i]记录i之前最多可以有多长,而j记录着i之后可以有多长,对每次取最大值即可。
此方法可过90%数据,10%WA。问题就出在:如果两序列收尾相连,我们就会在连接处多记一次导致出错;而我们在开始给mark[i]记为j - i的话,当两个序列不首尾相连,我们又会少记一个数,非常无语

90% CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long

using namespace std;

int n, k, ans;
int maxm = -inf;
int a[50005], mark[50005];

int main(){
	cin >> n >> k;
	for(int i = 1; i <= n; ++i){
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	
	int num = 0;
	for(int i = 1, j = 1; i <= n; ++i){
		num = a[j] - a[i];
		while(j <= n && num <= k){
			mark[j] =max(mark[j], j - i + 1);
			j++;
			num = a[j] - a[i];
		}
		maxm = max(maxm, mark[i]);
		ans = max(ans, maxm + j - i);
	}
	
	cout << ans << endl;
	
	return 0;
}

最终版

我们用mark[i]记录i之前最多能放多少个(不包括i),这样一来,每当i往前枚举时,记录的序列就不会重合,不用考虑边界问题了。

AC CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long

using namespace std;

int n, k, ans;
int maxm = -inf;
int a[50005], mark[50005];

int main(){
	cin >> n >> k;
	for(int i = 1; i <= n; ++i){
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	
	c[1] = 1;
	int num = 0;
	for(int i = 1, j = 1; i <= n; ++i){
		num = a[j] - a[i];
		while(j <= n && num <= k){
			mark[j + 1] = max(mark[j + 1], j - i + 1);
			j++;
			num = a[j] - a[i];
		}
		maxm = max(maxm, mark[i]);
		ans = max(ans, maxm + j - i);
	}
	
	cout << ans << endl;
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值