20201030CSP提高组训练(小鱼吃大鱼&超级蚯蚓&大鱼吃小鱼)


小鱼吃大鱼


题目

题目描述

小P同学在养殖一种非常凶狠的鱼,而且与其他鱼类不同,这种鱼越大越温顺,反而小鱼最凶残。当两条鱼相遇时, 小鱼会不断撕咬大鱼,每一口都咬下与它自身等重的肉(小鱼保持体重不变),直到大鱼的体重小于这条小鱼(若 两条鱼体重相同,一条鱼会将另一条撕咬殆尽)。

现在池塘中有n条鱼,小P想知道哪一对鱼相遇后,被咬的鱼剩余体重最大。
输入格式

单组测试数据。
第一行包含一个整数n,表示鱼的数量。(1 ≤ n ≤ 2e6) 第二行有n个用空格分开的整数ai 表示第i条鱼的体重(1 ≤ ai ≤ 1e6)。
输出格式

输出一个整数代表结果。
输入输出样例

输入 #1

3
3 4 5

输出 #1

2

输入 #2

2
2 2

输出 #2

0

输入 #3

5
2 1 4 3 5

输出 #3

2

说明/提示
数据范围

对于35%的数据,1≤n≤10,1 ≤ ai ≤ 100
对于55%的数据,1≤n≤10000
对于100%的数据,1 ≤ n ≤ 2e6,1 ≤ ai ≤ 1e6
样例解释

当三条鱼的体重分别为3 4 5时,不同对鱼相遇的结果分别是{3,4}=1 {3,5}=2 {4,5}=1,所以只有第一条跟第三条鱼相 遇时,最后大鱼的体重最大,结果为2

思路

全局最优没有之一!!!!!!!!!!!!!!!!!!
在这里插入图片描述

  1. 从小到大排序
  2. 去重(不去也能通过,但是两极分化(a的最大最小值差距很大)的数据会被卡飞)
  3. i从n到1枚举(主要为了优化)
  4. 若当前最优值>a[i]输出,结束程序(优化,具体自己想想)
  5. j枚举\(a[i]\)的倍数(到a数组的最大值,即排序后的\(a[n]\)
  6. \(tmp=j-1\)二分查找从n到i数第一个小于等于tmp的数,设其下标为\(l\)
  7. \(a[l] \% a[i] > ans\)更新ans

代码

我的代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#define rr register
using namespace std;
int read(){
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9'){
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		re = (re << 1) + (re << 3) + c - '0';
		c = getchar();
	}
	return re * sig;
}
int n;
int a[2000010];
int ans = 0;
int main() {
//	freopen("input.txt" , "r" , stdin);
//	freopen("output1.txt" , "w" , stdout);
	n = read();
	for(int i = 1 ; i <= n ; i++)
		a[i] = read();
	sort(a + 1 , a + n + 1);
	n = unique(a + 1 , a + n + 1) - a - 1;
	for(rr int i = n ; i > 0 ; i--){
		if(ans >= a[i])break;
//		cout << ans << endl;
		rr int l = i , r , mid;
		for(rr int j = a[i] * 2 ; j <= a[n] + a[i] ; j += a[i]){
			rr int goa = j - 1;
			if(goa <= i)continue;
			r = n;
			while(l < r){
				mid = (l + r) / 2;
				if((l + r) & 1)mid++;
				if(goa == a[mid]){
					l = mid;
					break;
				}
				if(goa < a[mid])	r = mid - 1;
				else l = mid;
			}
			if(a[l] % a[i] > ans)ans = a[l] % a[i];
		}
		
	}
	cout << ans;
	return 0;
}

参考题解代码

注:当输入的数组最小最大值差距很大的时候,参考代码会被卡到飞起

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200010, maxs = 1000010;
int n, a[maxn], f[maxs], ans;
int main() {
	freopen("input.txt" , "r" , stdin);
	freopen("output3.txt" , "w" , stdout);
	scanf("%d", &n);
	for(int i = 0; i < n; ++i) scanf("%d", a + i);
	sort(a, a + n);
	n = unique(a, a + n) - a;
	for(int i = 0; i < n; ++i)
		for(int j = a[i] + 1; j <= a[i + 1]; ++j)
			f[j] = a[i];
	for(int i = 0; i < n; ++i)  {
		for(int j = a[i] * 2; j <= a[n - 1]; j += a[i])
			ans = max(ans, f[j] % a[i]);
		ans = max(ans, a[n - 1] % a[i]);

	}
	printf("%d\n", ans);
	return 0;

}

其它解法的代码

详见:大佬博客

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
const int N=1e6;
int read() {
	int x=0,f=1; char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
	return x*f;
}
int n,ans,lg[N+10],f[N+10][21];
int get_min(int l,int r){
	if(r>N)r=N;int z=lg[r-l+1];
	return max(f[l][z],f[r-(1<<z)+1][z]);
}
int main()
{
	freopen("input.txt" , "r" , stdin);
	freopen("output2.txt" , "w" , stdout);
//	freopen("data.txt","r",stdin);
	n=read();
	for(int i=2;i<=N;i++)lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;i++){
		int x=read();
		f[x][0]=x;
	}
	for(int j=1;j<=20;j++)
		for(int i=1;i+(1<<j)-1<=N;i++)
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	for(int i=1;i<=N;i++)
		if(f[i][0]){
			for(int j=i;j<=N;j+=i)
				ans=max(ans,get_min(j,j+i-1)-j);
		}
	printf("%d",ans);
}

随机数据生成

#include <bits/stdc++.h>
using namespace std;
int random(int r , int l = 1){
	return (long long)rand() * rand() % (r - l) + l;
}
void output(int x){
	if(x >= 10)
		output(x / 10);
	putchar(x % 10 + '0');
}
int main(){
	srand((unsigned)time(0));
	freopen("input.txt" , "w" , stdout);
	int n = 2000000;
	printf("%d\n" , n);
	for(int i = 1 ; i <= n ; i++){
		if(random(10) >= 5)output(random(1000000));//此处专门用于生成两级分化的数据,大(小)数据的比重请调参(左边的“5”,即各占一半),可以把参考程序卡飞
		else output(random(100));
		putchar(' ');
	}
	return 0;
}
/*
	freopen("input.txt" , "r" , stdin);
	freopen("output.txt" , "w" , stdout);
	
*/

超级蚯蚓


题目

题目描述

生物学家们利用基因工程制造了一种超级蚯蚓,与原品种可以一分为二的特性相反,我们将两条这种超级蚯蚓的头 或尾端接触,他们的头或尾会连接起来。

实验室中现在有n条这样的超级蚯蚓,现在重复n次以下操作:随机抽出两条超级蚯蚓,使它们的头或尾接触。可以 想象,这样n次之后將不再有条状蚯蚓,n条超级蚯蚓连接成了一些环。那么有多大概率刚好所有这些超级蚯蚓只形 成了一个环?
输入格式

仅一行,包含一个整数n (2<=n<=1000)。
输出格式

输出一行,为刚好成环的概率。
输入输出样例
输入 #1

2

输出 #1

0.666667

输入 #2

5

输出 #2

0.406349

输入 #3

3

输出 #3

0.533333

说明/提示
数据范围

对于25%的数据,2<=n<=10
对于50%的数据,2<=n<=100
对于100%的数据,2<=n<=1000
样例解释

假设n=2,有2条超级蚯蚓,它们共有四个头/尾端,假设编号为ABCD,那么第一次选择AB或者CD不能成环,除此之 外选择AC AD BC BD都能成环,成环概率为4/(2+4)=2/3=0.666667

思路

很水的一道题
最后仅剩下一条环形蚯蚓,当且仅当(除最后一次外)每一次合并操作合并的两端均不是同一条蚯蚓
当有n条非环形蚯蚓时,共2n个端点合并到同一个蚯蚓的概率为\(\frac{1}{2n-1} \cdot \frac{1}{2n} \cdot 2n\)(2n个端点,第一次选2n种情况,第二次选(2n-1)种情况),则不成环的情况有\(1-\frac{1}{2n-1}\)
记f(n)为n条非环状蚯蚓合并后只有一个环的概率,则有:

\[\{ ^{f(n)=f(n-1)\cdot(1-\frac{1}{2n-1}),(n>1)} _{f(1)=1} \]

代码

#include <iostream>
#include <cstdio>
using namespace std;
double f[2];
int n;
int main(){
	cin >> n;
	f[1] = 1;
	for(int i = 2 ; i <= n ; i++){
		int now = i & 1 , pre = now ^ 1;
		f[now] = f[pre] * (1.0 - 1.0 / ((double)i * 2 - 1));
	}
	printf("%.6f" , f[n & 1]);
	return 0;
}

大鱼吃小鱼


题目

题目描述

小Q的家里养殖着一种肉食性的鱼,缺少食物的时候它们还会自相残杀,不过只有一条鱼的体重至少是另一条的两倍 时,体重更重的鱼才能吃掉另一条。

小Q想做一个实验,他把鱼两两一组装到入没有食物的鱼缸(如果鱼的数量是奇数则最后一个鱼缸内只有一条鱼), 请问要怎么分组最后鱼的总数最少,求出此时鱼的数量。
输入格式

单组测试数据。
第一行包含一个整数n(1≤n≤5e5)。
接下来n行,每行一个整数si,表示第i条鱼的大小 (1≤si≤1e5)。
输出格式

输出一个整数,即实验后鱼数量的最小值。
输入输出样例
输入 #1

8
2
5
7
6
9
8
4
2

输出 #1

5

输入 #2

5
1
2
3
4
5

输出 #2

3

输入 #3

3
2
2
1

输出 #3

2

说明/提示
数据范围

对于45%的数据,1≤N≤20,1≤si≤100
对于65%的数据,1≤N≤1000
对于100%的数据,1≤N≤5e5,1≤si≤1e5
样例解释

假设有8条鱼体重分别是{2,5,7,6,9,8,4,2},那么当分组情况为[2,6] [2,7] [4,8] [5,9]时,前三组大鱼都吃掉了小鱼,最 后一组的两条鱼都活了下来,此时所剩鱼的数量最少,为5条

思路

其实贪心并不难理解,就是题解像加了密一样奇奇怪怪的:

贪心的方法是将鱼按体重排序,然后分成2部分,只考虑让第1部分的吃掉第二部分。
因为:给我一个给鱼分组的方案,这方案里有k条鱼被吃掉了。那么,我总能对此方案进行优化,构造出新的分组方案,使得这个方案里也有k条鱼被吃掉了,其中,吃小鱼的大鱼是最大的k个,被吃掉的小鱼一定是最小的k个。那么最终剩下的数量 。利用双指针 可以处理。排序的复杂度为 ,因此总的复杂度为 。

先说说我的考试思路:
n <= 12无脑暴力DFS全排列(虽然45%数据都过不了)
其它:显然错误的贪心:
将s数组从小到大排序后,将数组划分为两半,右半部分为不能被吃掉的鱼,然后左右两半匹配
发现是错的eg:s={1,1,1,1,1,1,2,2,,8,9}
然后又来了一个思路:
将s数组从小到大排序后,将数组划分为两半,右半部分为不能被吃掉的鱼,左半边向下递归,结束后左边没有被吃掉的鱼和右半边的鱼匹配
发现上面的数据过了,题目样例却过不了
但是我们将两种思路的答案取小值,输出,我们就发现上面的数据和样例都可以过,说明正确率提高了
结果:60分(暴力+错误贪心)(含4TLE)
对拍证明n<=12时贪心的错误率为30%
数据真水啊

该说正解了:
其实也是贪心,首先从小到大排序,考虑到最多有一半的鱼被吃掉,那么我们令\(k=floor(n/2)\),左边的可能被吃掉,右边的负责吃,左右贪心匹配即可(i从1向右,j从\(ceil(n/2)\)向右,找第一个能吃掉i的鱼j:

	int i = 1 , j = n / 2 + 1;
	int ans = 0;
	for( ; i <= n / 2 ; i++){
		while(a[j] < a[i] * 2 && j <= n)++j;
		if(j > n)
			break;
		vis[i] = vis[j] = true;
		ans++;
		++j;
	}

正确性:

  • 最多只有一半的鱼会被吃掉:显然,因此我们让s更大的鱼吃s更小的鱼
  • 如果让左半边的鱼吃掉右半边的鱼:由于右半边的鱼不可被吞噬,且右半边不存在不够用的情况(因为是五五分),那么右半边的鱼就有可能存在浪费现象,显然让右边吃左边会更优
  • 左右贪心匹配(i从1向右,j从\(ceil(n/2)\)向右,找第一个能吃掉i的鱼j:如果让体重更大的鱼(设为k)吃i,那么体重大于i且能被k吃掉的鱼可能就无鱼能吃掉了,那么为何不让第一个能吃掉i的鱼j吃掉i呢
  • 如果跳过i鱼,不让i被吃:显然错误

代码

考场代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9'){
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		re = (re << 1) + (re << 3) + c - '0';
		c = getchar();
	}
	return re * sig;
}
int n;
int s[500010];
bool vis[500010];
int ans;

void dfs(int dep , int num){
	if(dep > n){
		if(num < ans){
			ans = num;
		}
		return;
	}
	if(dep == n)
		dfs(dep + 1 , num + 1);
	if(num > ans)return;
	for(int i = 1 ; i <= n ; i++)
		if(!vis[i]){
			vis[i] = true;
			for(int j = i + 1 ; j <= n ; j++)
				if(!vis[j]){
					vis[j] = true;
					if(s[j] < s[i] * 2)dfs(dep + 2 , num + 2);
					else{
						dfs(dep + 2 , num + 1);
						vis[j] = false;
						break;
					}
					vis[j] = false;
				}
			vis[i] = false;
		}
}
int k;
void search_(int left , int right){
//	cout << left << '\t' << right << endl;
	if(left >= right)return;
	int k;
	
	for(k = left ; k <= right ; k++){
		if(s[k] * 2 > s[right])break;
	}
	k--;
	search_(left , k);
//	ans = n - k;
	for(int i = k ; i >= left ; i--){
		if(vis[i])continue;
		int l , r , mid;
		l = k + 1 , r = right;
		while(l < r){
			mid = (l + r) >> 1;
			
			if(s[mid] >= s[i] * 2)r = mid;
			else	l = mid + 1;
		}
		if(left == 1 && right == 8 && i == 6){
//			cout << l << "!!!!!!!!!\n";
		}
		while(vis[l] && l <= right)l++;
		if(l <= right) vis[l] = true , vis[i] = true;
	}
	
	
/*	cout << left << '\t' << right << endl;
	for(int i = 1 ; i <= n ; i++)
		cout << vis[i] << '\t';
	cout << endl;*/
}
int main(){
//	freopen("input.txt" , "r" , stdin);
//	freopen("output1.txt" , "w" , stdout);
	n = read();
	for(int i = 1 ; i <= n ; i++)
		s[i] = read();
	sort(s + 1 , s + n + 1);
	if(n <= 12){
		ans = n;
		dfs(1 ,0);
		cout << ans;
	}
	
	else{
		for(k = 1 ; k <= n ; k++){
			if(s[k] * 2 > s[n])break;
		}
		k--;
		ans = n - k;
		for(int i = k ; i > 0 ; i--){
			int l , r , mid;
			l = k + 1 , r = n;
			while(l < r){
				mid = (l + r) >> 1;
				
				if(s[mid] >= s[i] * 2)r = mid;
				else	l = mid + 1;
			}
			while(vis[l] && l <= n)l++;
			if(l > n){
				ans++;
			}
			else vis[l] = true;
		}
		
		memset(vis , 0 , sizeof(vis));
		search_(1 , n);
		int cnt = 0;
		for(int i = 1 ; i <= n ; i++)
			if(vis[i])
				cnt++;
		int ans2 = cnt / 2 + n - cnt;
		if(ans == ans2)cout << ans;
		else cout << (ans < ans2 ? ans : ans2);
	}
	return 0;
}

满分代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int read(){
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9'){
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		re = (re << 1) + (re << 3) + c - '0';
		c = getchar();
	}
	return re * sig;
}
int n;
int a[500010];
bool vis[500010];
int main(){
	n = read();
	for(int i = 1 ; i <= n ; i++)
		a[i] = read();
	sort(a + 1 , a + n + 1);
	int i = 1 , j = n / 2 + 1;
	int ans = 0;
	for( ; i <= n / 2 ; i++){
		while(a[j] < a[i] * 2 && j <= n)++j;
		if(j > n)
			break;
		vis[i] = vis[j] = true;
		ans++;
		++j;
	}
	for(int i = 1 ; i <= n ; i++)
		if(!vis[i])
			ans++;
	cout << ans;
	return 0;
}

字符串水题


题目

题目描述

小 H 出了一道字符串水题,但他想要你帮他秒掉。

具体地,小 H 手上有一个数字串 S 。

于是他问出了 q 个问题,每个问题中包含另一个数字串 T 和两个非负整数 l, r ;他希望你对于 T 的每个在 S 中出 现过的、在 T 中出现位置不同的子串,确定当中有多少个子串,各位上的数字之和在 [l, r] 范围内。(即 T 中有两 个位置出现相同的子串,亦算作两个)
输入格式

第一行,一个字符串 S。
第二行,一个正整数 q。
以下 q 行,每行一个字符串 T 和两个非负整数 l, r。
输出格式

q 行,每行一个非负整数,表示答案。
输入输出样例
输入 #1

012321
3
123 3 3
2333 1 5
2333 5 11

输出 #1

2
5
1

说明/提示

数据范围
对于 30% 的数据,|S|, q, Σ|T| ≤ 10^3;
对于 100% 的数据,1 ≤ |S|, q, Σ|T| ≤ 2 × 10^5, 0 ≤ l, r ≤ 9|T|。
样例解释

对于第一个问题,有以下子串符合条件: 12,3 ,其各位数字和均为 3 。
对于第二个问题,有以下子串符合条件: 2,23,3,3,3 ,其各位数字和分别为 2,5,3,3,3 。
对于第三个问题,有以下子串符合条件: 23 ,其各位数字和为 5

思路

好“水”的题,好水的样例

暴力

外面套一层q,必须要的,输入T后处理前缀和,枚举T左右端点,判区间和是否在[l,r]内,再判区间是否为S的子串(原谅我至今不知find函数效率)
结果竟然有60分

正解

??????????????????

代码

暴力

#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
int read(){
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9'){
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		re = (re << 1) + (re << 3) + c - '0';
		c = getchar();
	}
	return re * sig;
}
string sread(){
	string re;
	char c = getchar();
	re.clear();
	while(c < '0' || c > '9')		c = getchar();
	while(c >= '0' && c <= '9')
		re += c,c = getchar();
	return re;
}

int q;
int sum[100010];
string s;
inline int calc(int l , int r){
	return l == 0 ? sum[r] : sum[r] - sum[l - 1];
}
int main() {
	s = sread();
	q = read();
	while(q--){
		string t = sread();
		int l = read() , r = read();
		int siz = t.size();
		sum[0] = t[0] - '0';
		for(int i = 1 ; i < siz ; i++)
			sum[i] = sum[i - 1] + t[i] - '0';
		int ans = 0;
		for(int i = 0 ; i < siz ; i++)
			for(int j = i ; j < siz ; j++){
				int tmp = calc(i , j);
				if(tmp > r)break;
				if(tmp >= l){
					if(s.find(t.substr(i , j - i + 1)) != string::npos)
						ans++;
				}
			}
		printf("%d\n" , ans);
	}
	return 0;
} 
posted @ 2020-11-11 11:02  追梦人1024  阅读(597)  评论(0编辑  收藏  举报