二分专题(不排除非二分)
Dropping tests
In a certain course, you take n tests. If you get ai out of bi questions correct on test i, your cumulative average is defined to be
.
Given your test scores and a positive integer k, determine how high you can make your cumulative average if you are allowed to drop any k of your test scores.
Suppose you take 3 tests with scores of 5/5, 0/1, and 2/6. Without dropping any tests, your cumulative average is . However, if you drop the third test, your cumulative average becomes .
Input
The input test file will contain multiple test cases, each containing exactly three lines. The first line contains two integers, 1 ≤ n ≤ 1000 and 0 ≤ k < n. The second line contains n integers indicating ai for all i. The third line contains n positive integers indicating bi for all i. It is guaranteed that 0 ≤ ai ≤ bi ≤ 1, 000, 000, 000. The end-of-file is marked by a test case with n = k = 0 and should not be processed.
Output
For each test case, write a single line with the highest cumulative average possible after dropping k of the given test scores. The average should be rounded to the nearest integer.
Sample Input
3 1
5 0 2
5 1 6
4 2
1 2 7 9
5 6 7 9
0 0
Sample Output
83
100
Hint
To avoid ambiguities due to rounding errors, the judge tests have been constructed so that all answers are at least 0.001 away from a decision boundary (i.e., you can assume that the average is never 83.4997).
分析:01分数规划,《挑战程序设计竞赛》上的最大化平均值;
01分数规划链接:http://www.cnblogs.com/zzqsblog/p/5450361.html
AC代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<algorithm>
#define N 1010
#define INF 1000000000
#define mem(a,b) memset(a,b,sizeof(a));
typedef long long ll;
using namespace std;
ll a[N],b[N];
double f[N];
int n,k;
bool cmp(double w,double v)
{
return w>v;
}
int Deal(double x)
{
for (int i=0;i<n;i++)
f[i]=a[i]-x*b[i];
double sum=0;
sort(f,f+n,cmp);
for (int i=0;i<n-k;i++)
sum+=f[i];
return sum>=0;
}
void erfen()
{
double l=0,r=2;
while ((r-l)>1e-6)
{
double mid=(l+r)/2;
if (Deal(mid))
l=mid;
else
r=mid;
}
printf("%.0lf\n",l*100);
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
while (scanf("%d%d",&n,&k))
{
if (!n&&!k)
break;
for (int i=0;i<n;i++)
scanf("%lld",&a[i]);
for (int i=0;i<n;i++)
scanf("%lld",&b[i]);
erfen();
}
return 0;
}
排列转换
现在有两个长度为n的排列p和s。要求通过交换使得p变成s。交换 p[i]和 p[j]的代价是|i-j|。要求使用最少的代价让p变成s。
Input单组测试数据。
第一行有一个整数n (1≤n≤200000),表示排列的长度。
第二行有n个范围是1到n的整数,表示排列p。每个整数只出现一次。
第三行有n个范围是1到n的整数,表示排列s。每个整数只出现一次。Output输出一个整数,表示从排列p变到s最少要多少代价。
Sample Input
样例输入1
4
4 2 1 3
3 2 4 1
Sample Output
样例输出1
3
分析 :交换 p[i]和 p[j]的代价是|i-j|,所以我们求p[i]当前的位置和p[i]应该在的位置的绝对值,然后将它们相加,但是我们要注意最后的结果要除以2,因为一个数在左移的时候,与它交换位置的那个数同时在右移
例如案例1:4在s中的3位置,ans+=2,ans=2; 2不用动;1在s中的4位置,ans+=1,ans=3;3在s中的1位置,ans+=3,ans=6;最后结果为ans/2;
AC代码:
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define N 200010
typedef long long ll;
using namespace std;
ll a[N],b[N];
ll f[N];
int main()
{
ll n;
cin>>n;
for (ll i=1;i<=n;i++)
{
cin>>a[i];
f[a[i]]=i;
}
ll ans=0;
for (ll i=1;i<=n;i++)
{
cin>>b[i];
ans+=abs(f[b[i]]-i);
}
cout << ans/2 << endl;
return 0;
}
Subsequence
Input
Output
Sample Input
2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5
Sample Output
2
3
本题思想:题目要求选取最少连续的数字个数使得它们的和大于等于S
①时间复杂度O(n)=nlogn
先求出前缀和sum,对于sum数组来说,如果满足条件sum[t]-sum[s]>=S则满足题目的条件,确定好起点s以后,我们用二分法求出sum中第一个大于等于sum[s]+S的数据,为什么不是S呢?如果是大于等于S的话那不就是相当于都是以第一个元素作为起点吗?对吧!!!
——|————————|—————————————|—————>
sum[s] —S—— sum[t] sum[n]
我们从sum[s]到sum[n]中间查找大于等于S的区间S,即红色的区域,这就是为什么查找的条件大于等于sum[s]+S了!最后求出最小的差区间即可
普通二分AC代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<queue>
#include<set>
#include<vector>
#include<stack>
#include<algorithm>
#define N 100100
#define mem(a,b) memset(a,b,sizeof(a))
#define abs(x) return x>0?x:(-x);
#define sqr(x) return x*x;
#define INF 999999999
typedef long long ll;
using namespace std;
ll a[N];
int main()
{
ll n,m;
int t;
cin>>t;
while (t--)
{
// ll num,mum;
cin>>n>>m;
ll sum=0;
for (ll i=1;i<=n;i++)
cin>>a[i];
ll l=1,r=1;
ll ans=INF;
ll flag=0;
while (1)
{
while (sum<m&&l<=n)
{
sum+=a[l];
l++;
}
if (sum<m)
break;
ans=min(ans,l-r);
sum-=a[r++];
}
if (ans<=n)
cout << ans << endl;
else
cout << "0" << endl;
}
}
下面讲一下复杂度为O(n)的算法:尺取法
传说中的尺取法:反复地推进区间的开头和末尾,来求满足条件的最小区间的方法称为尺取法。
②我们先定起点为第一个数字,然后一直往后相加到和大于等于S,记录当前的最小区间,然后减去当前的第一个数字,如果数组里面的数字还没用完并且当前的和小于S的话,就继续往后相加,直到大于等于S,不断进行前面的这些操作,直到不满足条件退出,中间记录当前的最小区间,如果所有的数字加起来都本鞥大于等于S的话,就输出0。
尺取法代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
typedef long long ll;
using namespace std;
#define N 100100
ll a[N];
int n;
ll k;
void solve()
{
ll sum=0,s=0,t=0;
ll ans=n+1;
while(1)
{
while (sum<k&&t<n)
{
sum+=a[t];
t++;
}
if (sum<k)
break;
ans=min(ans,t-s);
sum-=a[s++];
}
if (ans>n)
ans=0;
cout << ans << endl;
}
int main()
{
int t;
cin>>t;
while (t--)
{
cin>>n>>k;
for (int i=0;i<n;i++)
cin>>a[i];
solve();
}
return 0;
}
第K大的数
数组A和数组B,里面都有n个整数。数组C共有n^2个整数,分别是A00 * B00,A00 * B11 ......A11* B00,A11 * B11......An−1n−1 * Bn−1n−1(数组A同数组B的组合)。求数组C中第K大的数。
Input第1行:2个数N和K,中间用空格分隔。N为数组的长度,K对应第K大的数。(2 <= N <= 50000,1 <= K <= 10^9)
第2 - N + 1行:每行2个数,分别是Aii和Bii。(1 <= Aii,Bii <= 10^9)Output输出第K大的数。Sample Input
3 2
1 2
2 3
3 4
Sample Output
9
分析:题目有最多有50000个数字,暴力是肯定不能解决的吧!那就二分吧(刷的就是个二分专题)!那怎么二分呢?我一直找不到二分的方法,就觉得一定需要排序,排序好之后想是不是有简单的数学方法推出来呢!后来马上用一个例子推翻了自己的想法!后来看题解说是二分答案(其实这个专题有将近一半题目要用到二分答案,打算认真的归纳一下二分答案的解题),我们排序后,两个数组的最大值相乘就是整个和的最大值,最小值一样的,然后再最大值和最小值之间二分;二分的重点就是Check()函数,一定要向上取整,假设15/6的话,答案是2,但是比2大数字与6相乘不一定就会大于15,比如2*7;虽然AC了,但是我还是有个问题不能理解啊,比如输入一组数据
3 2
1 2 3
2 3 5
第二大的应该是10啊!但是代码运行的结果输出是9!这我就不是很能理解了!
AC代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<queue>
#include<set>
#include<vector>
#include<map>
#define N 50050
#define INF 1e+9
typedef long long ll;
using namespace std;
//coding......
ll a[N],b[N];
int n,k;
int C(ll mid)
{
ll num=0;
for (int i=n-1;i>=0;i--)
{
ll ans=mid/a[i];
if (mid%a[i])
ans++;
num+=n-(lower_bound(b,b+n,ans)-b);
}
return num>=k;
}
void solve()
{
ll lb=a[0]*b[0],ub=a[n-1]*b[n-1];
while (ub-lb>1)
{
ll mid=(lb+ub)/2;
if (C(mid))
lb=mid;
else
ub=mid;
}
cout << lb << endl;
}
int main()
{
cin>>n>>k;
for (int i=0;i<n;i++)
cin>>a[i]>>b[i];
sort(a,a+n);
sort(b,b+n);
solve();
return 0;
}
Face The Right Way
Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing forward, like good cows. Some of them are facing backward, though, and he needs them all to face forward to make his life perfect.
Fortunately, FJ recently bought an automatic cow turning machine. Since he purchased the discount model, it must be irrevocably preset to turn K (1 ≤ K ≤ N) cows at once, and it can only turn cows that are all standing next to each other in line. Each time the machine is used, it reverses the facing direction of a contiguous group of K cows in the line (one cannot use it on fewer than K cows, e.g., at the either end of the line of cows). Each cow remains in the same *location* as before, but ends up facing the *opposite direction*. A cow that starts out facing forward will be turned backward by the machine and vice-versa.
Because FJ must pick a single, never-changing value of K, please help him determine the minimum value of K that minimizes the number of operations required by the machine to make all the cows face forward. Also determine M, the minimum number of machine operations required to get all the cows facing forward using that value of K.
Lines 2.. N+1: Line i+1 contains a single character, F or B, indicating whether cow i is facing forward or backward.
7
B
B
F
B
F
B
B
Sample Output
3 3
Hint
AC代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 5010
using namespace std;
//coding.......
char a[N];
int dir[N];
int visit[N];
int n;
int Check(int k)
{
memset(visit,0,sizeof(visit));
int num=0,sum=0,i;
for (i=0;i+k<=n;i++)
{
if ((dir[i]+sum)%2)
{
visit[i]=1;
num++;
}
sum+=visit[i];
if (i-k+1>=0)
sum-=visit[i-k+1];
}
for (i=n-k+1;i<n;i++)
{
if((dir[i]+sum)%2)
return -1;
if (i-k+1>=0)
sum-=visit[i-k+1];
}
return num;
}
void solve()
{
int M=n,K=1,m;
for (int k=1;k<=n;k++)
{
m=Check(k);
if (m>=0&&M>m)
{
M=m;
K=k;
}
}
cout << K << ' ' << M << endl;
}
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
cin>>a[i];
if (a[i]=='F')
dir[i]=0;
else
dir[i]=1;
}
solve();
}