枚举、模拟、递推
枚举、模拟、递推
一、枚举模拟递推
-
状态空间:一个实际问题的各种可能情况构成的集合
-
递推和递归:程序遍历状态空间
枚举和模拟:将状态空间按照一定的顺序"直接"交给程序进行遍历的算法
-
按规模大小
枚举形式 | 一般遍历方式 |
---|---|
多项式 | 循环、递推 |
指数 | 递归、位运算 |
排列 | 递归、c++ next_permutation |
组合 | 递归+剪枝 |
- Strange Towers of Hanoi (POJ1958)
n个盘子4座塔的Hanoi问题至少需要多少步?(1<=n<=12)
分析:
n盘3塔: \(d[n] = 2*d[n-1]+1\) => \(d[n] = 2^n - 1\)
- 前n-1盘子 A -> B
- 第n盘子 A -> C
- 前n-1盘子 B -> C
n盘4塔:\(f[n] = min_{1\leq i<n}\{2*f[i] + d[n-i]\}\)
- 把i个盘子 A->B (四塔模式)
- 把n-i个盘子 A->D (三塔模式)
- 把i个盘子 B-> D (四塔模式)
- 考虑所有可能i取最小值
题解:
#include<iostream>
#include<cmath>
using namespace std;
int main(){
int f[13] = {0};
int minstep,step;
f[1] = 1;
for(int n=2;n<=12;n++){
minstep = 0x3f3f3f3f;
step=0;
for(int i=1;i<n;i++){
step = 2*f[i] + pow((float)2,n-i)-1; //POJ C++的pow格式严格
if(step<minstep)
minstep = step;
}
f[n] = minstep;
}
for(int n=1;n<=12;n++){
cout<<f[n]<<endl;
}
return 0;
}
二、前缀和
-
前缀和:前面i个数的总和
-
对于一个给定的数列A,它的前缀和数列S
\(s[i] = \sum_{j=1}^i A[j]\)
数列区间数的和 = 前缀和相减
\(sum(l,r) = \sum_{i=l}^r A[i] = S[r]-S[l-1]\)
-
可以扩展到二维数组中
例题:
- 激光炸弹(BZOJ1218)
一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标。现在地图上有n(N<=10000)个目标,用整数Xi,Yi(其值在[0,5000])表示目标在地图上的位置,每个目标都有一个价值。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。若目标位于爆破正方形的边上,该目标将不会被摧毁。
输入输出格式:
输入文件的第一行为正整数n和正整数R,接下来的n行每行有3个正整数,分别表示xi,yi,vi
输出文件仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过32767)。
输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
分析:
二维数组前缀和:一定区间里价值之和
\(S[i,j] = S[i-1,j] + S[i,j-1]-S[i-1,j-1]+A[i,j]\)
边长R的正方形的价值(ij为正方形的右下角)
\(P[i,j] = S[i,j] - S[i-R,j] - S[i,j-R] + S[i-R,j-R]\)
然后枚举正方形右下角找最大值
错误题解:(TLE MLE一堆 用于理解)
#include<iostream>
#define N 5000+5
using namespace std;
int A[N][N]={0}; //main函数里定义上限719 X 719
int S[N][N]={0};
int P[N][N]={0};
int main(){
int n,r,a,b,m;
cin>>n>>r;
m= 0;
while(n--){
int x,y,v;
cin>>x>>y>>v;
A[x][y] = v;
if(m<x) m=x;
if(m<y) m=y;
}
m = (m+r>5000)?5000:m+r;
for(int i=0;i<m;i++){
for(int j=0;j<m;j++){
a = (i-1<0)?0:i-1;
b = (j-1<0)?0:j-1;
S[i][j] = S[a][j]+S[i][b]-S[a][b]+A[i][j];
}
}
int max = 0;
for(int i=0;i<m;i++){
for(int j=0;j<m;j++){
a = (i-r<0)?0:i-r;
b = (j-r<0)?0:j-r;
P[i][j] = S[i][j] - S[a][j] - S[i][b] + S[a][b];
if(P[i][j]>max)
max=P[i][j];
}
}
cout<<max;
return 0;
}
题解:
#include<iostream>
#define N 5000+5
using namespace std;
int A[N][N]={0};
int main(){
int n,r,a,b,c,m;
cin>>n>>r;
m= 0; // 找个上限 缩短下运行时间
while(n--){
int x,y,v;
cin>>x>>y>>v;
A[x+1][y+1] += v;
if(m<x) m=x;
if(m<y) m=y;
}
m = (m+r>5000)?5000:m+r;
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
a = (i-1<0)?0:i-1;
b = (j-1<0)?0:j-1;
A[i][j] += A[a][j]+A[i][b]-A[a][b];
}
}
int max = 0;
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
a = (i-r<0)?0:i-r;
b = (j-r<0)?0:j-r;
c= A[i][j] - A[a][j] - A[i][b] + A[a][b];
if(c>max)
max=c;
}
}
cout<<max;
return 0;
}
- Tallest Cow(POJ3263)
给出N头牛的身高,和M对关系(ai与bi可以相互看见。即他们中间的牛都比他们矮)。已知最高的牛为第P头,身高为H。求每头牛的身高最大可能是多少。( \(1 \leq N,M \leq 10^4, 1 \leq H \leq 10^6\) )
输入样例:
9 3 5 5
1 3
5 3
4 3
3 7
9 8
输出样例:
5
4
5
3
4
4
5
5
5
分析:
朴素解法:数组C初始化0 ,ai与bi之间的数减一 最后设C[P] = 0 即Hi = H+C[i] \(O(NM)\)
前缀和:把对一个区间的操作转化为左右两个端点的操作,再通过前缀和得到原问题的解
数组D初始化0,D[ai+1]-=1,D[bi]+=1 最后 \(C[i] = \sum_{j=1}^iD[j]\) \(O(N+M)\)
题解:
#include<iostream>
#include<map>
using namespace std;
map<pair<int,int>,bool> existed; //处理重复的成对关系
int c[10010],d[10010];
int main(){
int n,p,h,m;
cin>>n>>p>>h>>m;
for(int i=1;i<=m;i++){
int a,b,t;
cin>>a>>b;
if(a>b){t=a;a=b;b=t;}
if(existed[make_pair(a,b)])continue;
d[a+1]--;d[b]++;
existed[make_pair(a,b)] = true;
}
for(int i=1;i<=n;i++){
c[i] = c[i-1] + d[i];
cout<<h+c[i]<<endl;
}
return 0;
}