zoukankan      html  css  js  c++  java
  • 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
    ", 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);
    	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 << '	' << 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 << "!!!!!!!!!
    ";
    		}
    		while(vis[l] && l <= right)l++;
    		if(l <= right) vis[l] = true , vis[i] = true;
    	}
    	
    	
    /*	cout << left << '	' << right << endl;
    	for(int i = 1 ; i <= n ; i++)
    		cout << vis[i] << '	';
    	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
    " , ans);
    	}
    	return 0;
    } 
    
  • 相关阅读:
    react的50个面试题
    什么是宏队列跟微队列
    宏队列与微队列
    数组都有哪些方法
    vuex 跟 vue属性
    高阶组件
    如何创建视图簇(View cluster)-SE54/SM34
    ◆◆0如何从维护视图(Maintenace view)中取数据-[VIEW_GET_DATA]
    ◆◆0如何在SM30维护表时自动写入表字段的默认值-事件(EVENT)
    ◆◆0SAP Query 操作教程
  • 原文地址:https://www.cnblogs.com/dream1024/p/13957629.html
Copyright © 2011-2022 走看看