【説明する】分治
拉呱
分治算法的基本思想是将一个规模为 N 的问题分解为 K 个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。
——以上来自百度百科。
* 分治法解题的一般步骤:
1 分解,将要解决的问题划分成若干规模较小的同类问题;
- 二分法:区间对半分开
2 求解,当子问题划分得足够小时,用较简单的方法解决;
- 边界情况:可以直接操作
3 合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。
- 合并操作:根据不同的题目来确定
例题:
-------------------------------T1+T2-------------------------------
其实这个题用冒泡排序做的,但用归并排序也能做出来(分析一下此题与逆序对是有相同之处的)
由于两者的代码完全一样,就只放一个啦 (>^ω^<)喵
1.洛谷 P1116 车厢重组
题目描述
在一个旧式的火车站旁边有一座桥,其桥面可以绕河中心的桥墩水平旋转。一个车站的职工发现桥的长度最多能容纳两节车厢,如果将桥旋转180度,则可以把相邻两节车厢的位置交换,用这种方法可以重新排列车厢的顺序。于是他就负责用这座桥将进站的车厢按车厢号从小到大排列。他退休后,火车站决定将这一工作自动化,其中一项重要的工作是编一个程序,输入初始的车厢顺序,计算最少用多少步就能将车厢排序。
输入输出格式
输入格式:
输入文件有两行数据,第一行是车厢总数N(不大于10000),第二行是N个不同的数表示初始的车厢顺序。
输出格式:
一个数据,是最少的旋转次数。
输入输出样例
4
4 3 2 1
2.洛谷 P1908 逆序对
题目描述
猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。
输入输出格式
输入格式:
第一行,一个数n,表示序列中有n个数。
第二行n个数,表示给定的序列。
输出格式:
给定序列中逆序对的数目。
输入输出样例
6
5 4 2 6 3 1
11
说明
对于50%的数据,n≤2500
对于100%的数据,n≤40000。
思路:
代码酱来也~
#include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
const int M = 4e4 + 1;
int n;
LL ans;
int a[M],b[M];
void gsort(int l,int r)
{
if(l==r) return;///不用再次排序
int m=(l+r)>>1;///取中间
gsort(l,m),gsort(m+1,r);
int i=l,j=m+1;
int k=l;///将a数组的值赋值到b数组
while(i<=m && j<=r)
{
if(a[i]<=a[j])
b[k++]=a[i++];///先赋值再++
else
{
b[k++]=a[j++];///同
ans+=(LL)(m)-(LL)i+1;///记录当前所形成的逆序对的个数
}
}
while(i<=m) b[k++]=a[i++];///赋值剩余没有进行比较的
while(j<=r) b[k++]=a[j++];///同
for(int i=l;i<=r;i++)
a[i]=b[i];///排完序后将排好序的在赋值回去
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
gsort(1,n);
printf("%lld\n",ans);
return 0;
}
--------------------------------T3--------------------------------
3.Codevs 1688 求逆序对
给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目
数据范围:N<=105。Ai<=105。时间限制为1s。
第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数。
所有逆序对总数.
4
3
2
3
2
3
思路:
代码酱来也~
#include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
const int M = 1e5 + 1;
int n;
LL ans;
int a[M],b[M];
void gsort(int l,int r)
{
if(l==r) return;///不用再次排序
int m=(l+r)>>1;///取中间
gsort(l,m),gsort(m+1,r);
int i=l,j=m+1;
int k=l;///将a数组的值赋值到b数组
while(i<=m && j<=r)
{
if(a[i]<=a[j])
b[k++]=a[i++];///先赋值再++
else
{
b[k++]=a[j++];///同
ans+=(LL)(m)-(LL)i+1;///记录当前所形成的逆序对的个数
}
}
while(i<=m) b[k++]=a[i++];///赋值剩余没有进行比较的
while(j<=r) b[k++]=a[j++];///同
for(int i=l;i<=r;i++)
a[i]=b[i];///排完序后将排好序的在赋值回去
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
gsort(1,n);
printf("%lld\n",ans);
return 0;
}
--------------------------------T4--------------------------------
4.洛谷 P1115 最大子段和
题目描述
给出一段序列,选出其中连续且非空的一段使得这段和最大。
输入输出格式
输入格式:
输入文件maxsum1.in的第一行是一个正整数N,表示了序列的长度。
第2行包含N个绝对值不大于10000的整数A[i],描述了这段序列。
输出格式:
输入文件maxsum1.out仅包括1个整数,为最大的子段和是多少。子段的最小长度为1。
输入输出样例
7
2 -4 3 -1 2 -4 3
4
说明
【样例说明】2 -4 3 -1 2 -4 3
【数据规模与约定】
对于40%的数据,有N ≤ 2000。
对于100%的数据,有N ≤ 200000。
思路:
这道题真的就是一道模板题,但是为什么我交了好几遍就是没有AC呢?
原因出在第二个点上,因为第二个点里的数据似乎全部都是负的,所以在进行初始化的时候需要赋值为一个极小数(ans,lmax,rmax这三个)
不然按一般的话,一定是从0开始,所以负数就出不来,所以……
代码酱来也~
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long
using namespace std;
const int M = 2e5 + 233;
int n;
LL ans;
int a[M];
LL calc(int l,int r)
{
if(l==r) return a[l];///不用再次排序
int m=(l+r)>>1;///取中间
LL ret=max(calc(l,m),calc(m+1,r));
/* find the maxl and maxr */
int maxl=-1e6,maxr=-1e6;
for(int i=m,s=0;i>=l;i--) s+=a[i],maxl=max(maxl,s);
for(int i=m+1,s=0;i<=r;i++) s+=a[i],maxr=max(maxr,s);
ret=max(ret,(LL)(maxl)+(LL)(maxr));
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
ans=-1e6;
ans=max(ans,calc(1,n));
printf("%lld\n",ans);
return 0;
}
--------------------------------T5--------------------------------
5.洛谷P1010 幂次方
题目描述
任何一个正整数都可以用2的幂次方表示。例如
137=2^7+2^3+2^0
同时约定方次用括号来表示,即a^b 可表示为a(b)。
由此可知,137可表示为:
2(7)+2(3)+2(0)
进一步:7= 2^2+2+2^0 (2^1用2表示)
3=2+2^0
所以最后137可表示为:
2(2(2)+2+2(0))+2(2+2(0))+2(0)
又如:
1315=2^10 +2^8 +2^5 +2+1
所以1315最后可表示为:
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
输入输出格式
输入格式:
一个正整数n(n≤20000)。
输出格式:
符合约定的n的0,2表示(在表示中不能有空格)
输入输出样例
1315
思路:
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> using namespace std; void work(int n) { if(n==1) { printf("2(0)"); return; } if(n==2) { printf("2"); return; } else { int j=1,i=0; do { j*=2; if(j>n)///超出该数n { j/=2; if(i==1) printf("2");///特判 else { printf("2(");///输出形式 work(i); printf(")"); } if(n-j!=0)///若还能够继续分解 { printf("+");///用+连接 work(n-j);///继续分解 } return; } else i++;///如果还不够大,继续加 }while(1); } } int main() { int n; cin>>n; work(n); return 0; }
--------------------------------T6--------------------------------
6.P1024 一元三次方程求解
题目描述
有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值>=1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。
提示:记方程f(x)=0,若存在2个数x1和x2,且x1<x2,f(x1)*f(x2)<0,则在(x1,x2)之间一定有一个根。
输入输出格式
输入格式:
一行,4个实数A,B,C,D。
输出格式:
一行,三个实根,并精确到小数点后2位。
输入输出样例
1 -5 -4 20
思路:
代码:
#include <iostream> #include <cstdio> #include <cmath> #define Db double using namespace std; Db a,b,c,d; Db f(Db x) { return a*x*x*x+b*x*x+c*x+d; } Db works(int x,int y) { Db x1=(Db)(x),x2=(Db)(y); Db mid=(x1+x2)/2,t1,t2; while(x1<=x2) { t1=f(mid); t2=f(x1); if(t1*t2<=0) x2=mid-0.0001; else x1=mid+0.0001; mid=(x1+x2)/2;///更新 } return mid; } int main() { scanf("%lf%lf%lf%lf",&a,&b,&c,&d); Db f1,f2; for(int v=-100;v<=100;v++) { // f1=a*v*v*v+b*v*v+c*v+d; f1=f(v); // f2=a*(v+1)*(v+1)*(v+1)+b*(v+1)*(v+1)+c*(v+1)+d; f2=f(v+1); if(f1==0) printf("%.2lf ",Db(v)); if(f1*f2<0) { Db ans=works(v,v+1); printf("%.2lf ",ans); } } return 0; }
--------------------------------T7--------------------------------
7.luogu P1083 借教室
题目描述
在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。
面对海量租借教室的信息,我们自然希望编程解决这个问题。
我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。
我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提
供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。
现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。
输入输出格式
输入格式:
第一行包含两个正整数n,m,表示天数和订单的数量。
第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。
接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在
第几天。
每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。
输出格式:
如果所有订单均可满足,则输出只有一行,包含一个整数 0。否则(订单无法完全满足)
输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。
输入输出样例
4 3 2 5 4 3 2 1 3 3 2 4 4 2 4
-1 2
说明
【输入输出样例说明】
第 1 份订单满足后,4 天剩余的教室数分别为 0,3,2,3。第 2 份订单要求第 2 天到
第 4 天每天提供 3 个教室,而第 3 天剩余的教室数为 2,因此无法满足。分配停止,通知第
2 个申请人修改订单。
【数据范围】
对于10%的数据,有1≤ n,m≤ 10;
对于30%的数据,有1≤ n,m≤1000;
对于 70%的数据,有1 ≤ n,m ≤ 10^5;
对于 100%的数据,有1 ≤ n,m ≤ 10^6,0 ≤ ri,dj≤ 10^9,1 ≤ sj≤ tj≤ n。
NOIP 2012 提高组 第二天 第二题
思路:
二分答案水过2333
代码:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int M = 1e6 + 8; int n,m; int ma[M],sum[M]; int d[M],s[M],e[M]; bool calc(int mid) { memset(sum,0,sizeof(sum)); for(int i=1;i<=mid;i++) { sum[s[i]]+=d[i];///表示从开始时间往后租借多少天 sum[e[i]+1]-=d[i]; } ///特判 if(sum[1]>ma[1]) return 1;///表示第一天就不够租借了 for(int i=2;i<=n;i++) { sum[i]+=sum[i-1];///前缀和,表示前几天一共有多少需要被租借 if(sum[i]>ma[i])///如果不够了 return 1; } return 0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&ma[i]); // sum[i]=ma[i]+sum[i-1]; } for(int i=1;i<=m;i++) scanf("%d%d%d",&d[i],&s[i],&e[i]); int l=1,r=m; while(l<r) { int mid=(l+r)>>1; if(calc(mid)) r=mid;///记录下出现差错的编号 else l=mid+1; ///让修改的人的编号再大一点,知道找出不成功的编号或者最终的成功 } if(m!=r) { printf("-1\n"); printf("%d",r); } else printf("0\n"); return 0; }
--------------------------------T8--------------------------------
8.luogu P2678 跳石头
题目背景
一年一度的“跳石头”比赛又要开始了!
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终 点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达 终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳 跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能 移走起点和终点的岩石)。
输入输出格式
输入格式:
输入文件名为 stone.in。
输入文件第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终 点之间的岩石数,以及组委会至多移走的岩石数。
接下来 N 行,每行一个整数,第 i 行的整数 Di(0 < Di < L)表示第 i 块岩石与 起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同 一个位置。
输出格式:
输出文件名为 stone.out。 输出文件只包含一个整数,即最短跳跃距离的最大值。
输入输出样例
25 5 2 2 11 14 17 21
4
说明
输入输出样例 1 说明:将与起点距离为 2 和 14 的两个岩石移走后,最短的跳跃距离为 4(从与起点距离 17 的岩石跳到距离 21 的岩石,或者从距离 21 的岩石跳到终点)。
另:对于 20%的数据,0 ≤ M ≤ N ≤ 10。 对于50%的数据,0 ≤ M ≤ N ≤ 100。
对于 100%的数据,0 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,000。
思路:
这道题可以用二分答案+贪心来做
首先我们定义l为0,r为给出的起点终点之间的长度,然后用二分答案做,将枚举到的答案检查一下是否行得通;
若行得通,继续寻找更优的答案,若行不通,寻找更小的答案;
二分答案是很好想的,也很好写,但是问题是check数组不太好想
我们根据题目的意思可以得到最多取走的石子数为m,所以如果当前答案已经给确定,但根据这个答案求出的最终取走的石子数要大于m,那么当前答案不成立
如何判断是否能被取走呢?因为题目为最短距离最大,所以只需判断从上一块石头跳到当前石头的距离是否比答案大即可;
如果大于,就跳上去,因为给出d数组是按照距离远近来的;反之若小于,就将这块石头取走,因为不满足题意.这既为check函数
代码:
#include <iostream> #include <cstdio> using namespace std; const int M = 5e4 + 1; int L,n,m; int d[M]; bool check(int x) { int last=0; int ans=0; for(int i=1;i<=n;i++) { if(d[i]-last<x)///make longer ans++; else last=d[i];///record } if(ans>m)///if out of range return 0; return 1; } int main() { scanf("%d%d%d",&L,&n,&m); for(int i=1;i<=n;i++) scanf("%d",&d[i]); int l,r,mid; l=0,r=L; while(l<=r) { mid=(l+r)/2; if(check(mid)) l=mid+1; else r=mid-1; } printf("%d\n",l-1); return 0; }
--------------------------------T9--------------------------------
9.luogu P1182 数列分段Section II
题目描述
对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列4 2 4 5 1要分成3段
将其如下分段:
[4 2][4 5][1]
第一段和为6,第2段和为9,第3段和为1,和最大值为9。
将其如下分段:
[4][2 4][5 1]
第一段和为4,第2段和为6,第3段和为6,和最大值为6。
并且无论如何分段,最大值不会小于6。
所以可以得到要将数列4 2 4 5 1要分成3段,每段和的最大值最小为6。
输入输出格式
输入格式:
输入文件divide_b.in的第1行包含两个正整数N,M,第2行包含N个空格隔开的非负整数A[i],含义如题目所述。
输出格式:
输出文件divide_b.out仅包含一个正整数,即每段和最大值最小为多少。
输入输出样例
5 3 4 2 4 5 1
6
说明
对于20%的数据,有N≤10;
对于40%的数据,有N≤1000;
对于100%的数据,有N≤100000,M≤N, A[i]之和不超过10^9。
思路:
这道题可以用二分答案+前缀和来做,但是我搞了好久依旧是80分,不知道哪里错了...
代码:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int M = 1e5 + 8; int n,m,maxx,minn; int sum[M],a[M]; int ans; bool calc(int now) { int mm=maxx; int cnt=0,last=0; int i=1; while(cnt<m) { while(sum[i]-sum[last]<=now) i++; i--; mm=mm-(sum[i]-sum[last]); last=i; cnt++; if(mm<=0) return 1; } return 0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); maxx+=a[i]; if(minn<a[i]) minn=a[i]; sum[i]=sum[i-1]+a[i]; } sum[n+1]=0x7fffffff;///因为之后要用到sum[n+1]来进行操作 int l=minn,r=maxx; while(l<=r) { int mid=(l+r)>>1; if(calc(mid)) r=(ans=mid)-1; else l=mid+1; } printf("%d",ans); return 0; }
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int M = 1e5 + 8; int n,m,maxx,minn; int sum[M],a[M]; int ans; bool calc(int now) { int tmp=0,total=0; for(int i=1;i<=n;i++) { if(total+a[i]<=now) total+=a[i]; else///若超出当前枚举到的答案最大值 { tmp++;///新的一段 total=a[i]; } } if(tmp<m) return 1; return 0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); maxx+=a[i]; if(minn<a[i]) minn=a[i]; } int l=minn,r=maxx; while(l<=r) { int mid=(l+r)>>1; if(calc(mid)) r=(ans=mid)-1; else l=mid+1; } printf("%d",ans); return 0; }
End.