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
思路
全局最优没有之一!!!!!!!!!!!!!!!!!!
- 从小到大排序
- 去重(不去也能通过,但是两极分化(a的最大最小值差距很大)的数据会被卡飞)
- i从n到1枚举(主要为了优化)
- 若当前最优值>a[i]输出,结束程序(优化,具体自己想想)
- j枚举\(a[i]\)的倍数(到a数组的最大值,即排序后的\(a[n]\))
- 令\(tmp=j-1\)二分查找从n到i数第一个小于等于tmp的数,设其下标为\(l\)
- 若\(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条非环状蚯蚓合并后只有一个环的概率,则有:
代码
#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;
}