问题 K: 【分治】化装晚会
题目:
万圣节又到了!FJ打算带他的奶牛去参加化装晚会,但是,FJ只做了一套能容下两头总长不超过S (1≤S≤1000000)的奶牛恐怖服装。FJ养了N(2≤N≤20000)头按1–N顺序编号的奶牛,编号为i的奶牛的长度为L_i(1≤L_i≤1000000)。如果两头奶牛的总长度不超过S,那么她们就能穿下这套服装。
FJ想知道,如果他想选择两头不同的奶牛来穿这套衣服,一共有多少种满足条件的方案。
输入
第1行是2个整数:N和S;
第2~N+l行每行一个整数:L_i。
输出
1个整数,表示FJ可选择的所有方案数。注意奶牛顺序不同的两种方案是被视为相同的。
思路:
首先想到可以排序后对每头牛i二分搜索和它满足条件的第二头牛j,,则比j长度小的牛也都满足条件,枚举第一头牛+二分搜索,复杂度O(nlogn)。
如果从大向小枚举i,那么其实没必要每次重新二分找j,因为满足前一头牛的j一定满足当前的牛。所以j可以按从小到大的顺序移动。复杂度降为O(n)。
细节见代码:
#include <iostream>
#include <algorithm>
#define rep(i,j,n) for(register int i=j;i>=n;i--)
using namespace std;
int a[20005];
int main()
{
int n,s;
cin>>n>>s;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
int cnt=0;
int j=1;
rep(i,n,1)
{
while(j<=n&&a[i]+a[j]<=s) j++;
if(i>=j) cnt+=j-1;
else cnt+=j-2;
}
cout << cnt/2 << endl;
return 0;
}
问题 C: 【分治】循环赛问题
题目:
设有n个选手的网球循环比赛,其中n=2k(0≤k<7)。现要设计一个满足以下条件的比赛日程表:
(1)每名选手要与其他n-1名选手都进行一次比赛;
(2)每名选手每天只赛一次;
(3)整个比赛共进行n-1天,要求每天没有选手轮空。
输入
选手人数n(n≤100),n只能为2的整数次幂。
输出
N阶方阵A[l…n,0…n-l],当j>0时,A[i,j]表示第i名运动员在第j天所遇到的比赛对手(A[i,0]=i),每个数据占5位宽度。
思路:
如下图所示是k=3时的一个可行解(第1列是选手编号),它是4块拼起来的。 左上角是k=2时的一组解,左下角是左上角每个数加4得到,而右上角、 右下角分别由左下角、 左上角复制得到。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
int ans[105][105];
void slove(int k,int n)
{
for(int i=1;i<=n;i++) ans[1][i]=i;
int start=1;
for(int p=1;p<=k;p++)
{
n/=2;
for(int t=1; t<=n; t++)
{
for(int i=start+1; i<=2*start; i++)//控制行
{
for(int j=start+1; j<=2*start; j++)//控制列
{
ans[i][j+(t-1)*start*2] = ans[i-start][j+(t-1)*start*2-start];//右下角等于左上角的值
ans[i][j+(t-1)*start*2-start] = ans[i-start][j+(t-1)*start*2];//左下角等于右上角的值
}
}
}
start *= 2;
}
}
int main()
{
int k=0,n;cin>>n;
int m=n;
while(m) m/=2,k++;
slove(k,n);
for(int i=1;i<=n;i++) {for(int j=1;j<=n;j++) printf("%5d",ans[i][j]);cout<<endl;}
return 0;
}
问题 R: 【分治】小车问题
题目:
甲、乙两人同时从A地出发要尽快同时赶到B地。出发时A地有一辆小车,可是这辆小车除了驾驶员外只能带一人。已知甲、乙两人的步行速度一样,且小于车的速度。问:怎样利用小车才能使两人尽快同时到达。
输入
仅一行,三个数据分别表示AB两地的距离s,人的步行速度a,车的速度b。
输出
两人同时到达B地需要的最短时间(答案保留两位小数)。
思路:
1)二分:根据题意可得知,小车载其中一人一定距离后,必要返回接另一个人,使得两人耗时相同且最小。
于是,我们就不妨二分小车第一次载甲的行驶距离S’(甲乙先后顺序不影响),计算出T甲,T乙。
如果T甲>T乙,则说明小车载甲的行驶距离太短,反之则太长。
代码:
#include<cstdio>
#include<iostream>
using namespace std;
double S,v1,v2;
int main()
{
scanf("%lf%lf%lf",&S,&v1,&v2);
double l=0,r=S,mid,t1,t2;
while(r-l>=0.0001)
{
mid=(l+r)/2;
t1=mid/v2+(S-mid)/v1;
t2=(2*mid*(v2-v1)+S*(v1+v2))/(v2*(v1+v2));
if(t1>t2)l=mid;
else r=mid;
}
printf("%0.4lf\n",l/v2+(S-l)/v1);
}
2)推公式
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
double s,v1,v2;
cin>>s>>v1>>v2;
double ans=s*(v2*v2-v1*v1)/(2*v1*v2-3*v1*v1+v2*v2);
double ANS=ans/v2+(s-ans)/v1;
printf("%0.2lf\n",ANS);
}
问题 M: 【分治】木材加工
题目:
木材厂有一些原木,现在想把这些木头切割成一些长度相同的小段木头(木头可能有剩余),需要得到的小段的数目是给定的。当然,我们希望得到的小段越长越好,你的任务是计算能够得到的小段木头的最大长度。木头长度的单位是cm。原本的长度都是正整数,我们要求得到的小段木头的长度也是正整数。
输入
第一行是两个正整数N和K(1≤N≤100000,1≤K≤200000),N是原木的数目,K是需要得到的小段的数目。接下来的N行,每行有一个1到10000之间的正整数,表示一根原木的长度L。
输出
输出能够切割得到的小段的最大长度。如果连lcm长的小段都切不出来,输出“0”.
思路:
令l=1,r=10000然后二分区间,若中点mid符合条件,则向右区间寻找,否则向左区间寻找,直到 l==r,r是最大的长度,输出r
#include <iostream>
#include <algorithm>
using namespace std;
int a[100005];
int n,k;
bool judge(int aver)
{
int cnt=0;
for(int i=1;i<=n;i++)
{
cnt+=a[i]/aver;
}
return cnt>=k;
}
int main()
{
cin>>n>>k;
int sum=0;
for(int i=1;i<=n;i++) cin>>a[i];
int l=1,r=10000,mid;
while(l<=r)
{
mid=(l+r)/2;
if(judge(mid)) l=mid+1;
else r=mid-1;
}
cout<<r<<endl;
return 0;
}
问题 O: 【分治】奇怪的函数
题目:
自从得到上次的教训后,John的上课态度认真多了,也变得更爱动脑筋了。今天他又学习了一个新的知识:关于 xk 的位数。
如果x大于0小于l,那么位数=1+小数部分×k,
如果x≥l,那么位数=trunc(ln(x)/ln(10)×k)+1+小数部分×k。
根据这些函数知识,他学会了求xk的位数了。但他又想到了另外一个问题,如果已知位数N,能不能求出使得 xk 达到或超过N位数字的最小正整数x是多少?
输入
输入一个正整数n(n≤2000000000)。
输出
输出使得 xk 达到n位数字的最小正整数x。
思路:二分区间[1,2000000000],是否符合条件 mid*log10(mid)+1>=n,是r=mid-1,否l=mid+1最后输出L即可;(与上一题M区分开来,M输出的是R)
代码:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n;cin>>n;
int l=1;int r=2e9;
while(l<=r)
{
int mid=(l+r)/2;
if(mid*log10(mid)+1>=n) r=mid-1;///是log10,log是以e为底的
else l=mid+1;
}
cout << l << endl;
return 0;
}