寒假集训总结
1. 1. 1.递推与递归:
递推:
S
t
e
p
1
Step1
Step1:猜测
T
a
Ta
Ta的递归式(推荐方法: 找规律,解析法)
S
t
e
p
2
Step2
Step2:用手算出前几个栗子
S
t
e
p
3
Step3
Step3:从a_i(第一个可以不用人脑的地方)开始算
P s Ps Ps:注意极限数据,such as “0”, “10^7”
例题
铺砖:
有2 * n的一个长方形方格道路,只有一种1 * 2的砖去铺,总共有多少种铺法呢?
a
i
=
a
i
−
1
+
a
i
−
2
a_i=a_{i-1}+a_{i-2}
ai=ai−1+ai−2
思路:
从最后一个要填的地方开始想:
两种情况,
1st:竖着填,直接占一"竖"
2nd:横着填,没办法,只有占"两竖"
递归:
S
t
e
p
1
Step1
Step1:找出题目中蕴含的递归关系
S
t
e
p
2
Step2
Step2:从头尾到尾头一步一步的走到初始条件
例题
2的幂次方表示:
任何一个正整数都可以用2的幂次方表示。
这里,我们将2n写作2(n),21就写作"2",例如:
137=2(2(2)+2+2(0))+2(2+2(0))+2(0)
思路
一步一步找到刚好比当前数字大的2的幂次,将Ta-1
输出加递归剩余的数字
注意:Ta的幂次也要分
最后的加号需要省略,打"\b"是没用的!!!
上代码
#include <bits/stdc++.h>
using namespace std;
void f(int n){
bool flag=false;
if(n%2) {
n-=1;
flag=true;
}
while(n>0){
if(n!=2){
cout << "2(";
int t=1,res=0;
while(t<=n){
t<<=1;
res++;
}
t>>=1;
// cout << endl << res << endl;
f(--res);//递归
cout << ")";
n-=t;
if(n!=0||flag) cout << "+";
}else{
cout << 2;
n-=2;
if(n!=0||flag) cout << "+";
}
}
if(flag==true) {
cout << "2(0)";
return;
}
}
int main() {
int n;
cin >> n;
f(n);
return 0;
}
2. 2. 2.分治
#主要思想:
“分而治之”,将一个非常大的问题拆成2或多个相对较小的问题,当问题给规模足够的小时,就花很少的时间将
T
a
Ta
Ta完成
然后----合并
例题
快速幂:
计算ab(mod 12345 12345 12345)的值
由一波小小的数学推导可以得到
a
b
=
{
(
a
b
/
2
)
2
b
=
2
⋅
k
(
(
a
(
b
−
1
)
/
2
)
2
)
⋅
b
b
=
2
⋅
k
+
1
a^b=\left\{ \begin{aligned} (a^{b/2})^2&&b=2\cdot k\\ ((a^{(b-1)/2})^2)\cdot b&&b=2\cdot k+1 \end{aligned} \right.
ab={(ab/2)2((a(b−1)/2)2)⋅bb=2⋅kb=2⋅k+1
所以,只需要打一个小小的递归就行了
3. 3. 3.分治的应用
Ⅰ . q u i c k s o r t Ⅰ.quick sort Ⅰ.quicksort
S
t
e
p
1
:
Step1:
Step1:取一个基准数
S
t
e
p
2
:
Step2:
Step2:将小于
T
a
Ta
Ta的数放在
T
a
Ta
Ta的左边,将大于
T
a
Ta
Ta的数放在
T
a
Ta
Ta的右边
S
t
e
p
3
:
Step3:
Step3:递归
T
a
Ta
Ta的左边和右边(对左边和右边分别从
S
t
e
p
1
Step1
Step1开始执行)
S
t
e
p
4
:
Step4:
Step4:结束(退出)
#include <cstdio>
#include <iostream>
using namespace std;
int a[300005], n;
void Swap(int &i, int &j) {
int t = i;
i = j;
j = t;
return;
}
void qsort(int l, int r) { //[l,r]
if (l >= r) {
return;
}
if (r - l == 1) {
if (a[r] < a[l]) {
Swap(a[r], a[l]);
}
return;
}
int m = l;
int p = l + 1, q = r;
while (p < q) {
while (a[q] >= a[m] && q > p) --q;
while (a[p] <= a[m] && q > p) ++p;
Swap(a[p], a[q]);
}
if (a[m] > a[p])
Swap(a[m], a[p]);
qsort(l, p - 1);
qsort(p, r);
return;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
qsort(1, n);
for (int i = 1; i <= n; ++i) {
printf("%d ", a[i]);
}
return 0;
}
P
s
:
Ps:
Ps:
快排的时间复杂度有可能到O(n2)所以推荐用随机基准数快排,但很难打…
运用:找数组第 k k k小的数
基准数的下标
若小于
k
k
k则递归右边
若大于
k
k
k则递归左边
若等于
k
k
k则输出return
Ⅱ . m e r g e s o r t Ⅱ.merge sort Ⅱ.mergesort
S
t
e
p
1
:
Step1:
Step1:从中间裂开
S
t
e
p
2
:
Step2:
Step2:当递归到底的时候就退出
S
t
e
p
3
:
Step3:
Step3:用双指针合并裂开的数组
S
t
e
p
4
:
Step4:
Step4:结束(退出)
#include <bits/stdc++.h>
using namespace std;
int n,a[100001];
void merge(int ll,int rr) {
int t[100001];
if(rr-ll<=1) return;
int mid=ll+(rr-ll>>1);
merge(ll,mid);
merge(mid,rr);
int p=ll,q=mid,s=ll;
while(s<rr){
if(p>=mid||(q<rr&&a[p]>a[q])) {
t[s++]=a[q++];
// ans += mid - p;
}else t[s++]=a[p++];
}
for(int i=ll;i<rr;++i) a[i]=t[i];
}
int main() {
cin >> n;
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
}
merge(1,n+1);
for(int i=1;i<=n;i++) {
cout << a[i] << " ";
}
return 0;
}
这个排序很稳定,O(n
⋅
\cdot
⋅log2n)的复杂度,会有意外的
运用:找逆序对
代码实现中注释掉的 ans += mid - p
就是在统计逆序对个数。具体来说,算法把靠后的数放到前面了(较小的数放在前面),所以在这个数原来位置之前的、比它大的数都会和它形成逆序对,而这个个数就是还没有合并进去的数的个数,即为 mid - p
。
摘自"oi-wiki.org"
4. 4. 4.前缀和与差分
前缀和:
s
u
m
1
=
a
1
sum_1=a_1
sum1=a1
s
u
m
i
=
s
u
m
i
−
1
+
a
i
,
i
>
1
sum_i=sum_{i-1}+a_i,i>1
sumi=sumi−1+ai,i>1
用于区间查询
差分:
c
f
1
=
a
1
cf_1=a_1
cf1=a1
c
f
i
=
a
i
−
a
i
−
1
,
i
>
1
cf_i=a_i-a_{i-1},i>1
cfi=ai−ai−1,i>1
用于区间修改
例题:叠干草
有 N N N(为奇数)堆干草,按 1.. N 1..N 1..N编号,开始时每堆高度都是 0 0 0。
F J FJ FJ给出 K K K条指令,每条指令包含两个用空格隔开的整数,例如"10 13",
表 示 给 10 , 11 , 12 , 13 表示给 10,11,12,13 表示给10,11,12,13这四堆干草分别叠加一捆干草,即高度均增加1。
FJ想知道,干草对完后,这 N N N堆干草高度的中位数是多少。
思路
利用差分可以区间修改的优势直接就暴力了
#include <bits/stdc++.h>
using namespace std;
int n,k,l,r,a[1000005];
int main() {
scanf("%d %d",&n,&k);
for(int i=1;i<=k;++i){
scanf("%d%d",&l,&r);
a[l]+=1;
a[r+1]-=1;
}
for(int i=1;i<=n;++i){
a[i]+=a[i-1];
}
sort(a+1,a+n+1);
cout << a[(n+1)/2];
return 0;
}
5. 5. 5.二分
定义:在适合的区间内查找最符合条件的值
实现方法:
1
s
t
:
1st:
1st:递归
2
n
d
2nd
2nd:while
运用:
1. 1. 1.查找函数零点
例题:
有函数:
f ( x ) = x 5 − 15 ⋅ x 4 + 85 ⋅ x 3 − 225 ⋅ x 2 + 274 ⋅ x − 121 f(x)=x^5-15\cdot x^4+85\cdot x^3-225\cdot x^2+274\cdot x-121 f(x)=x5−15⋅x4+85⋅x3−225⋅x2+274⋅x−121
已知 f ( 1.5 ) ⋅ f ( 2.4 ) ≤ 0 f(1.5)\cdot f(2.4)\le 0 f(1.5)⋅f(2.4)≤0.且方程在区间 [ 1.5 , 2.4 ] [1.5,2.4] [1.5,2.4]有且只有一个根,请用二分法求出该根。
要求四舍五入到小数点后6位。
首先认识一个东西:
零点定理,设函数
f
(
x
)
f(x)
f(x)在
[
a
,
b
]
[a,b]
[a,b]上连续,且
f
(
a
)
f(a)
f(a)与
f
(
b
)
f(b)
f(b)异号(即
f
(
a
)
⋅
f
(
b
)
<
0
)
f(a)\cdot f(b)<0)
f(a)⋅f(b)<0),那么在开区间
(
a
,
b
)
(a,b)
(a,b)内至少有函数
f
(
x
)
f(x)
f(x)的一个零点,即至少有一点
ξ
(
a
<
ξ
<
b
)
ξ(a<ξ<b)
ξ(a<ξ<b)使
f
(
ξ
)
=
0
f(ξ)=0
f(ξ)=0。
P
s
:
Ps:
Ps:仅为科普,对题目没有用
思路:
知道了区间,就只需要二分就行了...
注意:这里对精度的控制非常重要,除非你想空降...
#include <bits/stdc++.h>
#define d double
using namespace std;
d fffff(d x){
return x*x*x*x*x-15*x*x*x*x+85*x*x*x-225*x*x+274*x-121;
}
d f(d l,d r){
d mid=(l+r)/2;
d xxx=fffff(mid);
if(xxx==0){
return mid;
}
if(xxx>0){
return f(mid+0.0000001,r);
}else{
return f(l,mid-0.0000001);
}
}
int main() {
d a=1.5,b=2.4;//f(1.5)>0 f(2.4)<0
printf("%.6lf",f(a,b));
return 0;
}
//1.849016
2. 2. 2.查找元素:
例题:
查找最接近的元素
在一个非降序列中,查找与给定值最接近的元素。
思路:
( N o t h i n g ) (Nothing) (Nothing)
Ps:最基础的题目,可以应用在其他题目内
while(l<r){
if(l==r-1){
if(q-a[l]>a[r]-q){
l=r;
}
break;
}
int mid=(l+r)/2;
if(q>a[mid]) l=mid;
else if(q<a[mid]) r=mid;
else {
l=mid;
break;
}
}
3. 3. 3.查找最大且满足要求的值
例题:
网线主管
…(省略剧情)
编写一个程序,确定一个最长的网线长度(精确到厘米),并且按此长度对库存中的网线(所有网线的长度至少1m,至多100km)进行切割,能够得到指定数量的网线。
若无法得到长度至少为1cm的指定数量的网线,则必须输出“0.00”(不包含引号)。
思路:
二分网线长度,判断是否可行,注意精度
#include <bits/stdc++.h>
using namespace std;
int n,k;
double a[10005];
bool check(double x){
int tot=0;
for(int i=1;i<=n;++i){
tot+=floor(a[i]/x);
}
return tot>=k;
}
int main(){
int sum;
scanf("%d %d",&n,&k);
double kkk;
for(int i=1;i<=n;++i){
scanf("%lf",&kkk);
a[i]=kkk*100;
}
int l=0,r=10000000;
int mid;
while(r-l>1){
mid=(r+l)/2;
// cout << mid << endl;
if(check(mid)) l=mid;
else r=mid-1;
}
if(check(r))
printf("%.2lf",r/100.0);
else
printf("%.2lf",l/100.0);
return 0;
}
4. 4. 4.最大化平均值
例题:
疯牛
农夫John建造了一座很长的畜栏,它包括 N N N个隔间,这些小隔间依次编号为x1,…,xn。
但是,John的C头牛们并不喜欢这种布局,而且几头牛放在一个隔间里,他们就要发生争斗。为了不让牛互相伤害。John决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是什么呢?
思路:
二分间隔的距离,判断是否可行
#include <bits/stdc++.h>
using namespace std;
int n,a[100005],r=0,l=0,k;
int main() {
while(scanf("%d %d",&n,&k)!=EOF){
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
r=a[n]-a[1];
while(r-l>=1){
int mid=(l+r)/2;
int tot=1,X=1;
// cout << mid << endl;
for(int i=2;i<=n;++i){
if(a[i]-a[X]>mid){
X=i;
++tot;
}
}
if(tot>=k){
l=mid+1;
}else{
r=mid;
}
}
cout << endl << l << endl;
l=0;
}
return 0;
}
总结:
二分很难打,但如果能打,那一定是巅峰
6. 6. 6.贪心
前言:
猥琐的
T
a
Ta
Ta一步一步地走了过来
大致用法:
S
t
e
p
1
:
Step1:
Step1:确定贪心策略
S
t
e
p
2
:
Step2:
Step2:基于当前这一步,通过贪心策略去判断走哪边
缺点:
眼光太狭隘
解决方法:
D P . . . . . . DP...... DP......
入门:删数游戏
输入一个高精度的正整数S,去掉其中任意N个数字后剩下的数字按原左右次序将组成一个新的整数。 编程对给定的S和N,寻找一种方案使得剩下的数字组成的新数最小。 输入数据均不需判错。输出新的整数(S不超过255位)。
贪心策略:
删掉(从高位到低位)第一个升序的数字,注意前导0
for(int i=1;i<=n;i++){
for(int j=0;j<l;j++){
if(s[j+1]<s[j]){
for(int k=j;k<=l;k++){
s[k]=s[k+1];
}
l--;
break;
}
}
}
进阶:三大区间
前言:
T
a
Ta
Ta来了,
T
a
Ta
Ta来了,
T
a
Ta
Ta带着区间走来了…
为何无数初学者在贪心倒下,这究竟是区间的扭曲,还是区间的沦丧
分类:
区间选点问题:
若区间内没有①,则在区间的最后放
①:记录每个点的位置,二分
#include <bits/stdc++.h>
using namespace std;
struct q{
int b,e;
}a[100005];
bool cmp(q x,q y){
return x.e<=y.e;
}
int main() {
int n,c[100005],j=0;
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i].b >> a[i].e;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++){
if(i==1){
c[j]=a[i].e;
j++;
continue;
}
if(a[i].b<=c[j-1]);
else{
c[j]=a[i].e;
j++;
}
}
cout << j;
return 0;
}
区间覆盖问题:
最靠前①&&最靠后②的区间
①:头
≤
\le
≤上一个区间的尾
②:在满足①的情况下,尾最大的区间
#include <bits/stdc++.h>
using namespace std;
struct f{
int a,b;
}a[1000005];
bool cmp(f x,f y){
if(x.a>y.a) return false;
if(x.a==y.a&&x.b<y.b) return false;
return true;
}
int main() {
long long int n,s,t,tot=0;
cin >> n >> s >> t;
for(int i=1;i<=n;i++){
scanf("%d %d",&a[i].a,&a[i].b);
if(a[i].a>t||a[i].b<s) n--,i--;
}
if(n==0) {
cout << "No Solution";
return 0;
}
sort(a+1,a+n+1,cmp);
int i=1;
while(s<t&&i<=n){
if(a[i].a>s) {
cout << "No Solution";
return 0;
}
int max=0;
for(int j=i;j<=n;j++){
if(a[j].a>s) break;
if(a[j].b-s>max){
max=a[j].b-s;
i=j;
}
}
s=a[i].b,tot++,i++;
}
if(s>=t) cout << tot;
else cout << "No Solution";
return 0;
}
区间划分问题:
对于每个区间,判断在已知集合中有没有能够抗住加入的集合,
若无,则另开一个
若有,则加入
T
a
Ta
Ta
#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
struct f{
int b,e;
}a[100005];
bool cmp(f x,f y){
return x.b<y.b;
}
int main() {
int n,c[100005]={ },p=1;
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i].b >> a[i].e;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++){
int flag=0;
for(int j=p;j>=1;j--){
if(c[j]<=a[i].b){
c[j]=a[i].e;
flag=1;
break;
}
}
if(flag==0){
p++;
c[p]=a[i].e;
}
}
cout << p;
return 0;
}
/*
10
3 4
3 4
3 6
6 9
7 9
15 17
16 18
17 18
17 19
17 19*/
提高:观光公交(noi2011 D2 T3)
风景迷人的小城Y市,拥有 n n n个美丽的景点。由于慕名而来的游客越来越多,Y市特意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第 0 0 0分钟出现在1号景点,随后依次前往 2 、 3 、 4 … … n 2、3、4……n 2、3、4……n号景点。从第i号景点开到第 i + 1 i+1 i+1号景点需要 D i D_i Di分钟。任意时刻,公交车只能往前开,或在景点处等待。
设共有 m m m个游客,每位游客需要乘车 1 1 1次从一个景点到达另一个景点,第i位游客在Ti分钟来到景点Ai,希望乘车前往景点Bi(Ai<Bi) 。为了使所有乘客都能顺利到达目的地,公交车在每站都必须等待需要从该景点出发的所有乘客都上车后才能出发开往下一景点。假设乘客上下车不需要时间。
一个乘客的旅行时间,等于他到达目的地的时刻减去他来到出发地的时刻。因为只有一辆观光车,有时候还要停下来等其他乘客,乘客们纷纷抱怨旅行时间太长了。于是聪明的司机ZZ给公交车安装了 k k k个氮气加速器,每使用一个加速器,可以使其中一个Di减1。对于同一个Di可以重复使用加速器,但是必须保证使用后Di大于等于0。
那么ZZ该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?
贪心策略:
*计算在每段路使用氮气加速器的"价值",
在最值的路上使用
注意:每使用完一次就得刷新数据
#include <bits/stdc++.h>
using namespace std;
int n,d[100005],m,k,ttt,x,y;
int died[100005]={ },last[100005]={ },t[100005]={},people[100005]={ };
int main() {
int ans=0;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<n;++i){
scanf("%d",&d[i]);
}
for(int i=1;i<=m;++i){
scanf("%d%d%d",&ttt,&x,&y);
++died[y];
last[x]=max(last[x],ttt);
ans-=ttt;
}
while(k--){
memset(people,0,sizeof(people));
for(int i=2;i<=n;++i){
t[i]=max(last[i-1],t[i-1])+d[i-1];
}
for(int i=n;i>=2;--i){
if(d[i-1]>0){
people[i-1]=died[i];
if(t[i]>last[i]){
people[i-1]+=people[i];
}
}else{
people[i-1]=0;
}
}
int Max=0,maxx=-1;
for(int i=1;i<n;++i){
if(people[i]>Max){
Max=people[i];
maxx=i;
}
}
if(maxx==-1) break;
else --d[maxx];
}
memset(t,0,sizeof(t));
for(int i=2;i<=n;++i){
t[i]=max(last[i-1],t[i-1])+d[i-1];
}
for(int i=2;i<=n;++i){
ans+=t[i]*died[i];
}
cout << ans;
return 0;
}
7. D F S 7.DFS 7.DFS深度优先搜索
前言:这是一大难关,非常难 ~~~
简介
Ⅰ:深度优先搜索( D e p t h F i r s t S e a r c h Depth First Search DepthFirstSearch),注重的是搜索的深度,就是把每一条路都 走一遍,不走到底绝不回头
Ⅱ: T a Ta Ta运用的STL是栈(stack),也可以用树①来表示
Ⅲ: T a Ta Ta容易超时,但可以用剪枝②解决
Ⅳ: T a Ta Ta的关键技巧是回溯(" s u ˋ sù suˋ")③
①:暂无Link,请自行查阅《信息学奥赛一本通》
②:后面有讲到
③:就是在走完每一条路返回的时候,将走那一条路时修改的东西改回来
入门:迷宫问题
设有一个 N ∗ N N*N N∗N( 2 ≤ N ≤ 9 2\le N\le 9 2≤N≤9)方格的迷宫,入口和出口分别在左上角和右上角。迷宫格子中分别放 0 0 0和 1 1 1, 0 0 0表示可通, 1 1 1表示不能,入口和出口处肯定是 0 0 0。迷宫走的规则如下所示:
即从某点开始,有八个方向可走,前进方格中数字为 0 时表示可通过,为 1 时表示不可通过, 要另找路径。
找出所有从入口(左上角)到出口(右上角)的路径(不能重复),输出路径总数,如果无法到达,则输出 0 0 0。
思路
给出了起点和终点,直接从起点开始枚8个方向,如果不行就回溯,
每次dfs判断是否到达终点,若到达,则++ans
#include<bits/stdc++.h>
using namespace std;
int dirx[8]={-1,-1,-1,0,1,1,1,0},
diry[8]={-1,0,1,1,1,0,-1,-1};
int n,m,ans=0;
bool c[15][15];
void dfs(int i,int j){
if(i==1&&j==n){
++ans;
return;
}
c[i][j]=0;
for(int k=0;k<8;++k){
int x=i+dirx[k],y=j+diry[k];
if(x>0&&x<=n&&y>0&&y<=n&&c[x][y]){
dfs(x,y);
}
}
c[i][j]=1;
return;
}
int main() {
cin >> n;
int x;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
scanf("%d",&x);
if(x==1) c[i][j]=false;
else c[i][j]=true;
}
}
dfs(1,1);
cout << ans;
return 0;
}
进阶:关系网络
有 n n n个人,他们的编号为 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n,其中有一些人相互认识,现在 x x x想要认识 y,可以通过他所认识的人来认识更多的人(如果 a a a认识 b b b, b b b认识 c c c,那么 a a a可以通过 b b b来认识 c c c),求出 x x x最少需要通过多少人才能认识 y。
第 1 1 1行 3 3 3个整数 n 、 x 、 y , 2 ≤ n ≤ 100 n、x、y,2≤n≤100 n、x、y,2≤n≤100; 接下来的 n n n行是一个 n ⋅ n n\cdot n n⋅n的邻接矩阵,
aij=1 表示 i 认识 j,aij=0 表示不认识。 保证 i=j 时,aij=0,并且 aij=aji。(请仔细看,i j有一点难分辨)
思路
跟其他的题差不多,注意理解邻接矩阵
#include <bits/stdc++.h>
using namespace std;
int n,x,y;
bool fr[105][105];
bool fl[105];
int ans=0x7fffffff;
void dfs(int j,int tot){
if(tot>=ans) return;
// cout << j << " " << tot << endl;
if(j==y){
ans=min(ans,tot);
return;
}
fl[j]=1;
for(int i=1;i<=n;++i){
if(fl[i]||!fr[i][j]) continue;
dfs(i,tot+1);
}
fl[j]=0;
}
int main() {
scanf("%d %d %d",&n,&x,&y);
for(int i=1;i<=n;++i){
scanf("\n");
for(int j=1;j<=n;++j){
fr[i][j]=(bool)(getchar()-'0');
if(j<n) scanf(" ");
}
}
dfs(x,0);
cout << ans-1;
return 0;
}
提高:四色地图
思路
单纯的像上一题,用无向图存相邻的区块(链式前向星,)
代码
#include <bits/stdc++.h>
using namespace std;
int n;
bool fr[30][30];
int fl[105]={ };
bool pd(){
for(int i=1;i<=n;++i){
if(fl[i]==0) {
// printf("%d\n",i);
return 0;
}
}
return 1;
}
void dfs(int j){
bool cmp[5]={ };
for(int i=1;i<=n;++i){
if(!fr[i][j]) continue;
cmp[fl[i]]=1;
}
for(int i=1;i<5;++i){
if(!cmp[i]){
fl[j]=i;
bool flag=1;
for(int k=1;k<=n;++k){
if(!fl[k]){
dfs(k);
flag=0;
}
}
if(flag) break;
}
}
if(pd()){
for(int i=1;i<=n;++i){
printf("%d ",fl[i]);
}
exit(0);
return;
}
fl[j]=0;
return;
}
int main() {
scanf("%d",&n);
int x/*,z,y*/;
char y[105];
for(int i=1;i<=n;++i){
scanf("%d",&x);
gets(y);
for(int i=0;i<strlen(y);++i){
if(y[i]>='0'&&y[i]<='9'){
int out=0;
while(i<strlen(y)&&y[i]>='0'&&y[i]<='9'){
out=(out<<3)+(out<<1)+(y[i]^48);
++i;
}
fr[x][out]=fr[out][x]=1;
}
}
cerr << endl;
}
dfs(1);
return 0;
}
剪枝
1.优化搜索顺序
在不同的问题中,搜索树的各个层次、各个分支之间的顺序不是固定的,不同的搜索顺序会产生不同的搜索树形态,其规模大小也相差甚远。
实现:
s
o
r
t
sort
sort
2.排除等效冗余
在搜索过程中,若能判断从搜索树当前节点上沿某几条不同分支到达的子树是相同的,那么只需对其中一条分支执行搜索。
实现:
w
h
i
l
e
(
a
i
=
=
a
i
−
1
&
&
i
<
=
n
)
+
+
i
;
while(a_i==a_{i-1}\&\& i<=n) ++i;
while(ai==ai−1&&i<=n)++i;
3.可行性剪枝
可行性剪枝也叫上下界剪枝,其是指在搜索过程中,及时对当前状态进行检查,若发现分支已无法到达递归边界,就执行回溯。
实现:
i
f
(
if(
if(奇怪的边界
)
r
e
t
u
r
n
;
) return;
)return;
4.最优性剪枝
在最优化问题的搜索过程中,若当前花费的代价已超过当前搜索到的最优解,那么无论采取多么优秀的策略到达递归边界,都不可能更新答案,此时可以停止对当前分支的搜索,进行回溯。
实现:
i
f
(
if(
if(已有答案
(
(
(优于
)
)
)当前
)
r
e
t
u
r
n
;
) return;
)return;
5.记忆化搜索
在搜索过程中记录每个状态的结果,在重复遍历某个状态的时候直接返回其结果。
实现:
i
f
(
if(
if(当前位置已有数据
)
r
e
t
u
r
n
) return
)return那个数据
;
;
;
&&
r
e
t
u
r
n
return
return这里
=
=
=计算的数据