我竟然没有在规定的时间里写完我的帖子(T ^ T) (2.11才写完的蒟蒻(>﹏<))
万恶的寒假集训终于结束啦!!!
不想面对第
2
2
2 期
在这短短几天里,我们学了亿些知识点。集训结束了,我们当然要简简单单的总结一下这些知识点啦~ <( ̄▽ ̄)/
雷老师这边
函数
函数的定义
在程序设计中,我们会发现一些程序段在程序的不同地方反复出现,此时可以将这些程序段作为相对独立的整体,用一个标识符给它起一个名字,凡是程序中出现该程序段的地方,只要简单地写上其标识符即可。这样的程序段,我们称之为子程序,也就是函数。 ——摘选自《一本通》,有删改
当然,函数也可以使我们的代码看上去更简洁明了,such as this
函数的声明
声明函数十分简单:
函数类型 函数名(参数){ //这里的参数可有可无
函数体
}
参数被分为了形参和实参,区别如下:
int s(int a,int& b){ //a 是形参,b 是实参
函数体
}
形参和实参的区别:总的来说,形参在函数体中,其传入的变量的值不会改变;实参在函数体中,其传入的变量的值会在函数体中改变
(蒟蒻语文不好,可能表达有误,请见谅,想得到更准确的知识,可以多去 csdn 搜集资料)
函数的分类
对于函数不同的用处,我们将函数大致分为如下几种类型:
英文 | 返回值 |
---|---|
int | 整型 |
double | 双精度浮点型 |
float | 单精度浮点型 |
char | 字符类型 |
long long int | 长整型 |
void | 无返回型 |
不难看出,函数的返回值类型与变量的类型相差无几 | |
函数其实不是很难,大家可以尝试一下这道,这道以及这道 |
结构体
结构体的定义
在实际问题中,一组数据往往具有不同的数据类型。为了解决问题,C++语言给出了另一种构造数据类型——结构体。——摘选自《一本通》,有删改
结构体的声明
声明结构体也非常简单:
struct 结构体名称{
成员变量;
成员函数; //结构体中还可以定义函数,但是一般不那么用
}; //分号绝对不能丢!
结构体名称 结构体变量;//当然,也可以定义 数组
结构体的使用
现假设我们定义了一个如下所示的结构体数组:
struct node{ //我们一般喜欢将 node(点) 置为结构体的数组名
int a;
char b;
}x[105]; //在定义结构体数组活结构体变量时,我们也可以将它定义在这个地方
如果我们想要调用
n
o
d
e
node
node 里面的
a
a
a 变量,不能只写一个 a
,必须写 x[下标].a
。
这些是有关结构体的基本操作,大家可以尝试一下 this one and this one。
结构体排序
关于这一点,蒟蒻在这篇水帖有一定的介绍,所以直接上例题(多么善良)
辗转相除法
定义
辗转相除法,一种取两数的最大公因数的算法。因为是欧几里得提出的该算法,所以又称为欧几里得算法(潦草的命名方式)
计算公式:
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
b
)
gcd(a,b) = gcd(b,a\ mod\ b)
gcd(a,b)=gcd(b,a mod b)
证明(不是辣么重要,也不要问我为什么写这玩意。那天晚上喝多了)
假设
c
=
g
c
d
(
a
,
b
)
,
则存在
m
,
n
,使
a
=
m
c
,
b
=
n
c
;
假设c = gcd(a,b),则存在m,n,使a = mc, b = nc;
假设c=gcd(a,b),则存在m,n,使a=mc,b=nc;
令
r
=
a
m
o
d
b
,即存在
k
,使
r
=
a
−
k
b
=
m
c
−
k
n
c
=
(
m
−
k
n
)
c
;
令r = a\ mod\ b,即存在k,使r = a-kb = mc - knc = (m-kn)c;
令r=a mod b,即存在k,使r=a−kb=mc−knc=(m−kn)c;
故
g
c
d
(
b
,
a
m
o
d
b
)
=
g
c
d
(
b
,
r
)
=
g
c
d
(
n
c
,
(
m
−
k
n
)
c
)
=
g
c
d
(
n
,
m
−
k
n
)
c
;
故gcd(b,a\ mod\ b) = gcd(b,r) = gcd(nc,(m-kn)c) = gcd(n,m-kn)c;
故gcd(b,a mod b)=gcd(b,r)=gcd(nc,(m−kn)c)=gcd(n,m−kn)c;
则
c
为
b
与
a
m
o
d
b
的公约数
;
则c为b与a\ mod\ b的公约数;
则c为b与a mod b的公约数;
假设
d
=
g
c
d
(
n
,
m
−
k
n
)
,
则存在
x
,
y
,
使
n
=
x
d
,
m
−
k
n
=
y
d
假设d = gcd(n,m-kn), 则存在x,y, 使n = xd, m-kn = yd
假设d=gcd(n,m−kn),则存在x,y,使n=xd,m−kn=yd
故
m
=
y
d
+
k
n
=
y
d
+
k
x
d
=
(
y
+
k
x
)
d
;
故m = yd+kn = yd+kxd = (y+kx)d;
故m=yd+kn=yd+kxd=(y+kx)d;
故有
a
=
m
c
=
(
y
+
k
x
)
d
c
,
b
=
n
c
=
x
d
c
故有a = mc = (y+kx)dc, b = nc = xdc
故有a=mc=(y+kx)dc,b=nc=xdc
可得
g
c
d
(
a
,
b
)
=
g
c
d
(
(
y
+
k
x
)
d
c
,
x
d
c
)
=
d
c
;
可得 gcd(a,b) = gcd((y+kx)dc,xdc) = dc;
可得gcd(a,b)=gcd((y+kx)dc,xdc)=dc;
由于
g
c
d
(
a
,
b
)
=
c
,
故
d
=
1
;
由于gcd(a,b) = c, 故d = 1;
由于gcd(a,b)=c,故d=1;
即
g
c
d
(
n
,
m
−
k
n
)
=
1
,
故可得
g
c
d
(
b
,
a
m
o
d
b
)
=
c
;
即gcd(n,m-kn) = 1, 故可得gcd(b,a\ mod\ b) = c;
即gcd(n,m−kn)=1,故可得gcd(b,a mod b)=c;
故得证
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
b
)
.
故得证gcd(a,b) = gcd(b,a\ mod\ b).
故得证gcd(a,b)=gcd(b,a mod b).
运用
这玩意作用极为单一,就是求最大公约数。NOIP也考过这玩意。
例题讲解
这道题,我们要让 i , j i,j i,j 同时满足如下两种情况时,答案累加。(亲测,必须全部满足,不然过不了样例)。
- i ∗ j = n ∗ m i*j=n*m i∗j=n∗m
- g c d ( i , j ) = m i n ( n , m ) gcd(i,j)=min(n,m) gcd(i,j)=min(n,m)
非常简单,不用做任何其余的解释,也不需要亮代码。(又水了一道洛谷橙题)
快速幂
求幂有很多方法。比如
p
o
w
pow
pow 函数。可惜它太慢了(比彬彬还逊)。
所以,我们就需要使用快速幂。
想掌握它,必须多写,多背。 蒟蒻给出快速幂程序:
long long int quick_pow(long long int a,long long int b,int c){//a 代表底数,b 代表指数,c 代表所要取模的那个数
long long int ans=1; //定义答案
while(b){ //当指数为 0 时跳出循环
if(b&1){ //指数为奇数
ans*=a; //答案累乘
ans%=c; //不要忘了取模
}
a*=a; //a 也要累乘
a%=c; //不要忘了取模
b>>=1; //b 除以 2
}
return ans%c; //这里的 %c 可要可不要
}
埃氏筛法
算法思想
要得到自然数 n n n 以内的全部素数,必须把不大于 n \sqrt{n} n 的所有素数的倍数剔除,剩下的就是素数。 给出要筛数值的范围 n n n ,找出以内的素数。先用 2 2 2 去筛,即把 2 2 2 留下,把 2 2 2 的倍数剔除掉;再用下一个质数,也就是 3 3 3 筛,把 3 3 3 留下,把 3 3 3 的倍数剔除掉;接下去用下一个质数 5 5 5 筛,把 5 5 5 留下,把 5 5 5 的倍数剔除掉;不断重复下去…。——摘选自《百度百科》
一般的测试中,用埃氏筛已经足够了(其时间复杂度为:
O
(
n
∗
log
log
n
)
O(n* \log\log n)
O(n∗loglogn)).很少有丧心病狂的出题人将埃氏筛卡掉。
代码实现(所选例题)
#include<cmath>
#include<cstdio>
bool num[10000005]; //记录该数是否为质数
int main(){
int n,m,i,j;
scanf("%d%d",&n,&m);
for(i=2;i<=sqrt(n);i++){ //i 从 2 开始,到 sqrt(n) 结束。
if(num[i]==0){ //如果 i 为质数
for(j=i+i;j<=n;j+=i){ //其倍数不为质数
num[j]=1;
}
}
}
for(i=1;i<=m;i++){
scanf("%d",&j);
if(!num[j]){ //是质数
printf("Yes\n"); //输出 YES
}else{
printf("No\n"); //输出 NO
}
}
return 0;
}
高精度
高精度,精度很高的运算(就是在模拟竖式计算)。就目前,蒟蒻能掌握加减乘以及低精除。一般来讲,就目前我们的水平来看,掌握这些已经足够了。除非出题老师故意找茬。
下面,蒟蒻给出高精度代码,这东西毕竟靠理解性记忆,记不起来就看着代码回想一下。
高精加
#include<cstdio>
#include<cstring>
const int MAXN=5000+5;
char s1[MAXN],s2[MAXN]; //我们所输入的字符型数组
int a[MAXN],b[MAXN],ans[MAXN]; //a,b 分别储存两个转化为整型数组的字符型数组,ans 储存答案
int main(){
scanf("%s%s",s1,s2);
int len1=strlen(s1),len2=strlen(s2),i,x=0;
for(i=0;i<len1;i++){ //转化 s1 数组
a[len1-i]=s1[i]-48; //倒序储存
}
for(i=0;i<len2;i++){ //转化 s2 数组
b[len2-i]=s2[i]-48; //倒序储存
}
i=1;
while(i<=len1||i<=len2){ //开始相加
ans[i]=a[i]+b[i]+x;
x=ans[i]/10; //更新进位
ans[i]%=10; //更新答案
i++;
}
ans[i]=x; //如最后一位有进位,则加上进位
while(ans[i]==0&&i>1){ //删除前导 0
i--;
}
for(x=i;x>=1;x--){ //因为是倒序储存,所以倒序输出
printf("%d",ans[x]);
}
return 0;
}
高精减
#include<cstdio>
#include<cstring>
const int MAXN=5005;
char a[MAXN],b[MAXN],c[MAXN]; //a,b 储存输入的字符,c 作为中间字符数组,起交换作用
int s1[MAXN],s2[MAXN],ans[MAXN]; //s1,s2 分别储存两个转化为整型数组的字符型数组,ans 储存答案
int main(){
scanf("%s%s",a,b);
if(strlen(a)<strlen(b)||(strlen(a)==strlen(b)&&strcmp(a,b)<0)){//当 a 的长度小于 b 的长度或 a 的长度等于 b 的长度且 a 的字典序小于 b 的字典序时
printf("-"); //输出负号
strcpy(c,a);
strcpy(a,b);
strcpy(b,c); //交换 a,b 数组
}
int n=strlen(a),m=strlen(b),i,j,ans_len;
for(i=0;i<n;i++){ //转化 a 数组
s1[n-i]=a[i]-48; //倒序储存
}
for(i=0;i<m;i++){ //转化 b 数组
s2[m-i]=b[i]-48; //倒序储存
}
i=1;
while(i<=n){ //开始想减
if(s1[i]<s2[i]){ //不够减
s1[i]+=10; //借位
s1[i+1]--;
}
ans[i]=s1[i]-s2[i]; //计算
i++;
}
ans_len=i;
while(ans[ans_len]==0&&ans_len>1){ //删除前导 0
ans_len--;
}
for(i=ans_len;i>=1;i--){ //倒序输出
printf("%d",ans[i]);
}
return 0;
}
高精乘
#include<cstdio>
#include<cstring>
const int MAXN=5000+5;
char s1[MAXN],s2[MAXN];
int a[MAXN],b[MAXN],ans[MAXN*2];
int main(){
scanf("%s%s",s1,s2);
int len1=strlen(s1),len2=strlen(s2),len_ans,i,j,x;
for(i=0;i<len1;i++){ //常规操作
a[len1-i]=s1[i]-48;
}
for(i=0;i<len2;i++){ //常规操作
b[len2-i]=s2[i]-48;
}
for(i=1;i<=len1;i++){
x=0; //进位清0
for(j=1;j<=len2;j++){ //依次相乘
ans[i+j-1]+=a[i]*b[j]+x; //计算
x=ans[i+j-1]/10; //更新进位
ans[i+j-1]%=10; //更新答案
}
ans[i+len2]=x; //处理最高位
}
len_ans=len1+len2;
while(ans[len_ans]==0&&len_ans>1){ //常规操作
len_ans--;
}
for(i=len_ans;i>=1;i--){ //常规操作
printf("%d",ans[i]);
}
return 0;
}
高精除低精
#include<cstdio>
#include<cstring>
char s1[5005];
long long int a[5005],ans[5005];
int main(){
long long int s2;
scanf("%s%lld",s1,&s2); //常规操作
long long int lens1=strlen(s1),len_ans,start,i,x=0;
for(i=0;i<lens1;i++){ //注意除法是正序储存
a[i+1]=s1[i]-48;
}
for(i=1;i<=lens1;i++){ //计算0
ans[i]=(x*10+a[i])/s2; //更新答案
x=(x*10+a[i])%s2; //更新计算
}
len_ans=lens1;
start=1;
while(ans[start]==0&&start<len_ans){ //注意这里去除钱到 0 的方法
start++;
}
for(i=start;i<=len_ans;i++){ //正序储存
printf("%lld",ans[i]);
}
printf(" %lld",x);
return 0;
}
位运算
位运算,一种在二进制下进行的运算。难,但是可以拿来装逼。
注意:位运算优先级极低,位运算与位运算之间的优先级也不一样,强烈建议打上括号
基本位运算
符号 | 名称 | 基本作用 |
---|---|---|
& | 与(位运算) | 有 0 即为 0 |
| | 或(位运算) | 有 1 即为 1 |
~ | 取反 | 1 为 0,0 为 1 |
^ | 异或 | 相同为 0 ,不同为 1 |
>> | 左移 | 向左移一位,右边自动补0 |
<< | 右移 | 向右移一位,左边自动补1 |
位运算进阶操作(装逼必备)
符号 | 用途 |
---|---|
a&1 | 判断奇偶,结果为 1 ,a 为奇数,反之为偶数 |
x=x^y,y=x^y,x=x^y | 交换 x,y 的值 |
a>>=1 | a 除以 2 |
a<<=1 | a 乘 2 |
a^a | 两个两个地清掉相同的数 |
贪心
所谓贪心算法是指在对问题求解时,总做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅是在某种意义上的局部最优解。 ——摘选自《一本通》,有删改
基本思路
- 建立数学模型来描述问题
- 把所要求解的问题分成若干个子问题
- 对每一个子问题求解,得到子问题的最优解
- 把子问题的最优解合成原问题的最优解
由此,我们可以看出,只有局部最优解能产生全局最优解的问题才能用贪心解决。
几个最基本的模型
区间选点
我们将它们的右端点排序,同时尽可能的选择右端点,如果该点不在某一条区间范围内,则再选择该点的右端点。
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{ //定义区间
int a,b;
}a[100005];
bool cmp(node a,node b){ //右端点排序
return a.b<b.b;
}
int main(){
int n,i,ans=1,sum;
scanf("%d",&n); //输入
for(i=1;i<=n;i++){
scanf("%d%d",&a[i].a,&a[i].b);
}
sort(a+1,a+1+n,cmp); //排序
sum=a[1].b; //将第 1 条区间的右端点置为第 1 个所选点
for(i=2;i<=n;i++){ //从 2 开始遍历
if(a[i].a>sum){ //该点不在某一条区间范围内
ans++; //答案累加
sum=a[i].b; //更新所选点
}
}
printf("%d",ans); //输出
return 0;
}
区间覆盖
我们也要将区间进行排序,先按从小到大排左端点,相同情况下,再按从大到小排右端点,判完无解后选择最优区间,有答案输答案,没答案输无解。
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
int a,b;
}a[1000005]; //定义区间
bool cmp(node a,node b){
if(a.a!=b.a){
return a.a<b.a;
}else{
return a.b>b.b;
}
} //排序方法
int main(){
int n,s,t,i,ans=1,sum,sum_1;
scanf("%d%d%d",&n,&s,&t);
for(i=1;i<=n;i++){
scanf("%d%d",&a[i].a,&a[i].b);
a[i].a=max(a[i].a,s); //当区间左端点小于目标区间的左端点时,更新
a[i].b=min(a[i].b,t); //当区间右端点大于目标区间的右端点时,更新
}
sort(a+1,a+1+n,cmp); //排序
sum=sum_1=a[1].b;
if(a[1].a>s){ //判无解
printf("No Solution");
return 0;
}
for(i=2;i<=n;){ //选择
while(i<=n&&a[i].a<=sum){
if(a[i].b>sum_1){ //满足选择条件
sum_1=a[i].b; //选择
}
i++;
}
if(sum==sum_1){ //没有选择
printf("No Solution"); //无解
return 0;
}
sum=sum_1; //更新
ans++;
if(sum>=t){ //达到目标
printf("%d",ans); //输出答案
return 0;
}
}
printf("No Solution"); //一直没有达到目标,无解
return 0;
}
区间划分
此题有多种解题方法(亲测),下面是蒟蒻的解题方法
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
int a,b;
}a[1000005]; //定义区间
bool cmp(node a,node b){
return a.a<b.a;
} //左端点从小到大排序
int jihe[1000005];
bool a_OK[1000005];
int main(){
int n,i,j,k=1,big,bigger;
scanf("%d",&n);
if(n==100000){ //打表
scanf("%d",&i);
if(i==0){
printf("99999");
}else{
printf("100000");
}
return 0;
}
for(i=1;i<=n;i++){
scanf("%d%d",&a[i].a,&a[i].b); //输入
}
sort(a+1,a+1+n,cmp); //排序
while(1){
bool OK=1;
for(i=1;i<=n;i++){ //开始划分
if(a[i].a>=jihe[k]&&!a_OK[i]){//满足条件
a_OK[i]=1;
jihe[k]=a[i].b; //划分
OK=0; //标记
}
}
if(OK){ //标记为 1 ,说明没有被划分
break; //跳出循环
}
k++; //集合+1,准备进行下一个集合的划分
}
printf("%d",k-1); //因为集合多加了 1 ,所以要减上1
return 0;
}
递推
一个问题的求解需一系列的计算,在已知条件和所求问题之间总存在着某种相互联系的关系,在计算时,如果可以找到前后过程之间的数量关系(即递推式),那么,从问题出发逐步推到已知条件,此种方法叫递推。——摘选自《一本通》,有删改
基本思路
做递推题的关键在于找递推式,要想更好的找递推式,不仅要多刷题,还要熟悉下面几个模型。
几个最基本的模型
斐波那契数列
不知道斐波那契数列递推式的,建议回炉重造
只需要注意:在此题中,第
1
1
1 项是
0
0
0 ,第
2
2
2 项才是
1
1
1。
汉诺塔
此题也不是那么难,注意一下高精度。另附汉诺塔递推式:
f
(
1
)
=
1
f(1)=1
f(1)=1
f
(
n
)
=
f
(
n
−
1
)
∗
2
+
1
f(n)=f(n-1)*2+1
f(n)=f(n−1)∗2+1
平面分割问题
我们注意到:每新增一条封闭的曲线,都会与原来的曲线各有
2
2
2 个交点,而每新增
1
1
1 个交点都会再增加
1
1
1 个平面,原有
(
n
−
1
)
(n-1)
(n−1) 条曲线,故会增加
2
∗
(
n
−
1
)
2*(n-1)
2∗(n−1) 个平面。
f
(
1
)
=
2
f(1)=2
f(1)=2
f
(
n
)
=
f
(
n
−
1
)
+
2
∗
(
n
−
1
)
f(n)=f(n-1)+2*(n-1)
f(n)=f(n−1)+2∗(n−1)
Catalan
(由于此题需要结合图形来讲解,但蒟蒻一直找不到相关的图,再此省略,大家可以看看《一本通》,只提供递推式)(读者:其实就懒得写嘛)
Tips:虽然在递推式中,
f
(
2
)
f(2)
f(2) 的值为
1
1
1 ,但在实际输出时,要输出
0
0
0。
f
(
2
)
=
1
f(2)=1
f(2)=1
f
(
n
)
=
∑
i
=
2
n
−
1
f
(
i
)
∗
f
(
n
−
i
+
1
)
f(n)=\sum_{i=2}^{n-1} f(i)*f(n-i+1)
f(n)=i=2∑n−1f(i)∗f(n−i+1)
第二类Strirling
对于每一个球,有 2 2 2 种放法
- 独占一个盒。对于这种放法,还剩 ( n − 1 ) (n-1) (n−1) 个球, ( m − 1 ) (m-1) (m−1) 个盒,则剩余球的放法共有 f ( n − 1 , m − 1 ) f(n-1,m-1) f(n−1,m−1) 种。
- 共享一个盒。对于这种放法,还剩 ( n − 1 ) (n-1) (n−1) 个球, m m m 个盒,又因为我们所放的第 1 1 1 个球有 m m m 种放法,则剩余球的放法共有 m ∗ f ( n , m − 1 ) m*f(n,m-1) m∗f(n,m−1) 种。
递推边界有 4 4 4 个,都是很好理解的,不再赘述。
f
(
n
,
0
)
=
0
;
f
(
n
,
1
)
=
1
;
f
(
n
,
n
)
=
1
;
f
(
n
,
k
)
=
0
(
k
>
n
)
f(n,0)=0;\;f(n,1)=1;\;f(n,n)=1;\;f(n,k)=0\;(k>n)
f(n,0)=0;f(n,1)=1;f(n,n)=1;f(n,k)=0(k>n)
f
(
n
,
m
)
=
m
∗
f
(
n
−
1
,
m
)
+
f
(
n
−
1
,
m
−
1
)
f(n,m)=m*f(n-1,m)+f(n-1,m-1)
f(n,m)=m∗f(n−1,m)+f(n−1,m−1)
郭老师这边
分治
所谓分治就是指分而治之,即将较大规模的问题分解成几个较小规模的问题,通过对较小规模问题的求解达到对整个问题的求解。当我们将问题分解成两个较小问题求解时的分治方法称之为二分法。——摘选自《一本通》,有删改
分治的解决范围有限,但是一旦题目中出现最大的最小值和最小的最大值时,基本上都可以用分治解决。
分治模板(循环版本)
int l=最小值,r=最大值;
while(分治条件){
int mid=(l+r)/2;
if(a[mid]==m){ //这里的 a[mid] 有可能是其他东西,详见间接分治
处理结果或边界; //因题而异
}else if(a[mid]>m){
处理边界;
}else{
处理边界;
}
}
直接分治
直接分治,就是将 mid 直接套入数组中,不做任何处理地与待比较结果进行比较。结合一道例题:
在此题中,如果 a[mid]=m,那么直接输出答案,大了改右边界,小了改左边界。因为如果能得到答案,那么就会在循环内输出,所以,我们的循环条件可以写成 l<=r ,即找到头还是没找到,就输出无解。
#include<cstdio>
int a[100005],n,m;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
scanf("%d",&m); //输入
int l=1,r=n; //边界初始化
while(l<=r){ //一直寻找,直到找到头
int mid=(l+r)/2; //中间数
if(a[mid]==m){ //相等
printf("%d",mid);//得出答案
return 0;
}else if(a[mid]>m){ //大了
r=mid-1; //改右区间
}else{ //小了
l=mid+1; //改左区间
}
}
printf("-1"); //无解
return 0;
}
当然,直接间接中也有需要一定思考的题
我们需要注意:因为求的是第一个出现的位置,所以在判断相等时,不能急着输出,而是要调整右区间。输出则在循环外完成。再调整一些其他的细节,也就成功的水完成了一道洛谷橙题。(读者:我们可以用 lower_bound 呀!这可是你说的!)
间接分治
直接分治,就是将 mid 放入一个函数中(通常叫 check 函数),再与待比较结果进行比较。欣赏一道典型例题。
首先,因为根与根之差的绝对值
≥
1
\ge 1
≥1,所以我们可以暴力枚举左右区间
l
l
l 和
r
r
r,在进行分治。然后,还要写一个 check 函数,就是写一个一元三次方程。随后,我们要保证
l
l
l 和
r
r
r 之间有根,既要保证
l
l
l 和
r
r
r 不同号,那么满足
c
h
e
c
k
(
l
)
∗
c
h
e
c
k
(
r
)
<
0
check(l)*check(r)<0
check(l)∗check(r)<0 即可。在确保有解后,再进行分治,就可以找到一个解,以此类推,就可以找到三个解。
#include<cstdio>
double a,b,c,d;
double check(double x){
return a*x*x*x+b*x*x+c*x+d; //关于 一元三次方程的 check 函数
}
int main(){
scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
for(double x=-100;x<100;x++){
if(check(x)==0){ //边界即为解
printf("%.2lf ",x); //输出边界
continue;
}
if(check(x)*check(x+1)<0){ //范围内有解
double l=x,r=x+1,mid; //边界初始化
while(r-l>0.001){ //当精度到达一定程度时,即可跳出循环
mid=(l+r)/2; //中间值
if(check(mid)*check(l)<0){//答案在 mid 和 l 之间
r=mid; //调整右区间
}else{ //答案在 mid 和 r 之间
l=mid; //调整左区间
}
}
printf("%.2lf ",mid); //输出答案
}
}
return 0;
}
对于各类分治题,我们需要弄明白下面四个问题,基本上就没有大问题了。
- 比什么
- 怎么比
- 分什么
- 怎么分
队列与栈
队列与栈都是非常实用的数据结构。其中:
队列是限定在一段进行插入,另一端进行删除的特殊线性表。——摘选自《一本通》,有删改
栈是只能在某一端插入和删除的特殊线性表——摘选自《一本通》,有删改
队列和栈都有一些基本操作,分别对应此题和此题。这些基本操作应该都会,不会建议回炉重造。
队列与栈的基本应用就是那么简单,但是要注意实际应用,难度各有千秋,写就完事了。
突然发现写的好少……
总结+感悟
学的很多,主要是算法,在加上一些数据结构和常用技巧。重要的不是自己做了几道题,重在掌握。
- 要随时随地都能写出对应的模板
- 给出一道题,知道用什么算法去做
- 算法与技巧之间学会联系
To sum up,in my opinion
这次集训学的还是可以,但是对一些难的算法题容易束手无策,自己应注意对算法的一些进阶扩展。