常见的枚举形式
枚举形式 | 一般的遍历方法 |
---|---|
多项式 | 循环,递推 |
指数 | 递归,位运算 |
排列 | 递归 ,c++ next_permutation |
组合 | 递归+剪枝 |
实现指数型枚举
DFS (一)
思路是沿路径递增的形式输出。
#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
int n;
int path[100000];
void dfs(int stp, int k) {
for (int i = 0; i < k; ++i)
printf("%d ", path[i]);
putchar('\n');
if (stp > n)
return ;
for (int i = stp; i <= n; ++i) {
path[k++] = i; //选择
dfs(i + 1, k);
k -- ; //回溯,不选择
}
}
int main() {
scanf("%d", &n);
dfs(1, 0);
return 0;
}
DFS (二)
void calc(int x){
if(x == n+1)
{
for(int i =0;i<chosen.size();++i)
printf("%d ",chosen[i]);
putchar('\n');
return ;
}
//两种选择
calc(x+1); //不选
chosen.push_back(x); // 选
calc(x+1);
chosen.pop_back(); // 回溯
}
位运算(一)
// 每一个数都有选与不选两种可能,所以是2的n次方种
for (int i = 0; i < (1 << n); ++i) {
for (int j = 0; j < n; ++j)
if ((i >> j) & 1) //判断i的第j为是否选择
cout << j + 1 << " ";
cout << '\n';
}
位运算(二)
void dfs(int u,int state){
if(u == n){
for(int i=0;i<n;++i)
if(state>>i &1)
cout<<i+1<<" ";
cout<<endl;
return ;
}
dfs(u+1,state);
dfs(u+1,state|1<<u);//将state的第u位置为1
}
实现组合型枚举
DFS + 剪枝
#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
int n,m;
int path[100000];
void dfs(int stp, int k) {
if(k == m){ // 检测 放的元素是否达到m个
for (int i = 0; i < k; ++i)
printf("%d ", path[i]);
putchar('\n');
}
// 剪枝
//选的元素个数已经超过m个了,或者即使再选上剩余的所以数也不够m个
if(k >= m || (k+n-stp+1)<m)
return ;
if (stp > n)
return ;
for (int i = stp; i <= n; ++i) {
path[k++] = i;
dfs(i + 1, k);
k -- ;
}
}
int main() {
scanf("%d%d", &n,&m);
dfs(1, 0);
return 0;
}
作者:多元函数
链接:https://www.acwing.com/activity/content/code/content/1963737/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
实现排列型枚举
DFS
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int n;
bool vis[10];
int path[10];
void dfs(int sp){
if(sp == n+1){
for(int i=1;i<=n;++i)
printf("%d ",path[i]);
putchar('\n');
return ;
}
for(int i=1;i<=n;++i){
if(!vis[i]){ // 用一个bool 数组记录一条路径已经使用过的值,避免重复
vis[i] = 1;
path[sp] = i; //因为在这里sp就是sp对应的位置应该插入一个数
dfs(sp+1);
vis[i] = 0; // 回溯
}
}
}
int main(){
scanf("%d",&n);
memset(vis,0,sizeof(vis));
dfs(1);
return 0;
}
费解的开关
难点:
枚举第一行的所有状态后如何改变原来的,思路是对于位数为1的就改变,并计数。而因为改变会改变原来的状态,所以需要用一个数组来保存下原来的状态在改变之后在恢复回来。在定完第一行后就可以递推下面的行,行是从0~4的,如果为0就改变,之后就检验最后一行是否都是1。
技巧:
对于原来是0的字符变为1,是1的变为0.可以用异或1的方法。
g[xx][yy] ^= 1;
代码:
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
const int INF = 987654321;
using namespace std;
char g[20][20];
int ans;
int dx[5] = {0,1,-1,0,0},dy[5] ={0,0,0,1,-1};
void turn(int x,int y){
for(int i = 0;i<5;++i){
int xx = x+dx[i],yy = y+dy[i];
if(xx>=0&&xx<5&&yy>=0&&yy<5){
g[xx][yy] ^= 1;
}
}
}
int work(){
for(int k=0;k<(1<<5);++k){
int res = 0;
char barcup[20][20];
memcpy(barcup,g,sizeof g);
for(int i=0;i<5;++i)
if(k>>i &1){ // 位置为1表明该位置的灯变化
res++; //这所以每一步都要计数
turn(0,i);
}
for(int i =0;i<4;++i)
for(int j=0;j<5;++j){
if(g[i][j] == '0'){
res++;
turn(i+1,j);
}
}
bool istrue = true;
for(int i=0;i<5;++i)
if(g[4][i] == '0')
{
istrue = 0;
break;
}
if(istrue) ans = min(ans,res);
memcpy(g,barcup,sizeof g);
}
if(ans>6)return -1;
return ans;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
for(int i=0;i<5;++i)cin>>g[i];
ans = INF;
ans = work();
printf("%d\n",ans);
}
return 0;
}
奇怪的汉诺塔
代码:
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
const int INF = 98765431;
int n;
int d[100],f[200];
int main(){
n = 12;
d[1] = 1;
for(int i=2;i<=n;++i) // 递归算出三个盘子
d[i] = 2*d[i-1] + 1;
memset(f,0x3f,sizeof f);
f[0] = 0;
for(int i=1;i<=n;++i)
for(int j=0;j<i;++j)
// f[j] 是有四个塔的时候,把前j个搭移到一个搭上。
// 之后还要移动回来所以是*2
// 之后把剩余的i-j个移动的时候只有3个所以是 d[i-j]
f[i] = min(f[i],2*f[j]+d[i-j]);
for(int i =1;i<=n;++i)
printf("%d\n",f[i]);
return 0;
}
分形之城
递归+分治+数学坐标系公式+找规律
递归+分治好理解,因为这个题目中最显著的特点就是,不断地重复旋转复制,也就是NN级城市,可以由44个N−1N−1级城市构造,因此我们每次可以不断地分形N−1N−1级,将问题范围不断地缩小即可
这道题目的数学坐标公式,其实一共有两个,一个是高中的数学函数,旋转,这是一个难点,其实可以通过找规律,求解,第二公式则是欧几里得距离公式。(x1−x2)2−(y1−y2)2−−−−−−−−−−−−−−−−−−√(x1−x2)2−(y1−y2)2
最难的就是如何旋转这个正方形 找规律。
总的来说这道题目数学知识较多,考察画图能力,解法自然,数据毒瘤,相信可以给你的NOIP一个有利的一脚。
- 左上角:我们可以发现,左上角的N−1N−1级矩阵其实就是等级为N−1N−1,也就是上一个矩阵,顺时针旋转90°90°,那么既然如此的话,我们就可以综合yxc老师上课所讲的公式(补充:也就是旋转矩阵,属于大学的线性代数内容),得出转移后的矩阵中的一点坐标从(x,y)(x,y)变为(y,x)(y,x)
- 左下角:同左上角,它则是逆时针旋转90°90°而且还要水平翻转,也即是沿着XX轴对称,原本逆时针后为(y,−x)(y,−x),然后要对称,xx坐标不变,yy坐标取反,所以坐标为(−y,−x)(−y,−x) 也就是所谓的(2×len−1−y,len−1−x)(2×len−1−y,len−1−x) 最难理解的坐标,具体可以画图理解
- 右上角和右下角:通过N=2N=2级图发现,其实和N=1N=1是一样的,并没有旋转,只是平移,则右上角坐标为(x,y+len)(x,y+len),右下角坐标为(x+len,y+len)(x+len,y+len)
总的来说以上四种转移,都可以通过画图理解
还有本题数据毒瘤,四舍五入最好是double类型,而且整数类型一定要是long long,否则容易WA!
题解出处:https://www.acwing.com/solution/content/814/
小技巧:
坐标(x,y) 顺时针旋转一定度数后得到的坐标公式是:
(
x
,
y
)
∣
cos
θ
sin
θ
−
sin
θ
cos
θ
∣
(x,y) \left| \begin{matrix} \cos\theta & \sin\theta \\ -\sin\theta & \cos\theta \\ \end{matrix} \right|
(x,y)∣∣∣∣cosθ−sinθsinθcosθ∣∣∣∣
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
#define ll long long
#define PLL pair<ll,ll>
PLL calc(ll n,ll m)
{
if (n==0)
return make_pair(0,0);
ll len=1LL<<(n-1),cnt=1LL<<(2*n-2);
PLL pos=calc(n-1,m%cnt);
ll x=pos.first,y=pos.second;
ll z=m/cnt;
if (z==0)
return make_pair(y,x);
if (z==1)
return make_pair(x,y+len);
if (z==2)
return make_pair(x+len,y+len);
return make_pair(2*len-1-y,len-1-x);
}
int main()
{
//ios::sync_with_stdio(false);
int t;
scanf("%d",&t);
while(t--)
{
ll n,a,b;
scanf("%lld%lld%lld",&n,&a,&b);
PLL x=calc(n,a-1);
PLL y=calc(n,b-1);
ll dx=x.first-y.first,dy=x.second-y.second;
double ans=(sqrt(double(dx*dx+dy*dy))*10);
printf("%0.lf\n",ans);
}
return 0;
}