今日学习了状态压缩dp
用memset浮点数每两个字节为127为最大值,scanf读取double要%lf,保留两位小数输出用%.2f
==的优先级大于&!!!!!!,算了以后比较位运算都加括号算了
Acwing 91. 最短Hamilton路径
给定一张 𝑛 个点的带权无向图,点从 0∼𝑛−1 标号,求起点 0 到终点 𝑛−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到𝑛−1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 𝑛。
接下来 𝑛 行每行 𝑛 个整数,其中第 𝑖 行第 𝑗 个整数表示点 𝑖 到 𝑗 的距离(记为 𝑎[𝑖,𝑗])。
对于任意的 𝑥,𝑦,𝑧,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 𝑎[𝑥,𝑦]+𝑎[𝑦,𝑧]≥𝑎[𝑥,𝑧]。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
1≤𝑛≤20
0≤a[i,j]≤1e7
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18
思路:
本题如果暴力枚举每种方案取最小值大概复杂度就是n!*n(终点有n个),直接爆炸
使用dp状态压缩枚举每种方案并取最小值复杂度大概是(1<<20)*20//枚举方案2^20种,枚举路径20种,大搞1e7复杂度,刚刚好
状态表示为f[i,j]表示方案为i,从0走到j的代价,属性是最小值
对每种方案进行枚举,判断是否合法来更新最短距离
首先走到j就代表i方案里一定包含j,因此(i>>j)&1==1
其次用j的前一步来更新j的代价,因此i-(1<<j)一定要包含k,因此i>>k&1==1
接着就从k点到j的最小代价+w[k][j]即可
初始化起点到自己的距离为0,其他距离为无穷即可
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=20,M=1<<20;
int a[N][N],f[M][N];//路径状态为i,所有从0走到j的最小代价
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>a[i][j];
memset(f,0x3f,sizeof f);
f[1][0]=0;
for(int i=0;i<1<<n;i++)//方案
for(int j=0;j<n;j++){//走到j
if(i>>j&1){//能走到j
for(int k=0;k<n;k++)//走到j之前的k取最小代价
if(i-(1<<j)>>k&1)//这一步存在
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+a[k][j]);
}
}
cout<<f[(1<<n)-1][n-1];
//(1<<n)-1]刚好所有方案全是1,就代表所有点全走过
return 0;
}
P1433 吃奶酪
房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)点处。
输入格式
第一行有一个整数,表示奶酪的数量 n。
第 2 到第 (n+1) 行,每行两个实数,第 (i+1) 行的实数分别表示第 i 块奶酪的横纵坐标 xi,yi。
输出格式
输出一行一个实数,表示要跑的最少距离,保留 2 位小数。
对于全部的测试点,保证 1≤n≤15,∣xi∣,∣yi∣≤200,小数点后最多有 3 位数字
时间复杂度(1<<15)*15*15=大概4e6不会超时
本题要预处理出邻接矩阵,以及起始点是1-n个奶酪中的任何一个,终点也是1-n中的任何一个,且是一个无向完全图,因此可以用状态压缩DP来做
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define x first
#define y second
using namespace std;
const int N=16,M=1<<16;
typedef pair<double,double> PDD;
PDD a[N];
double w[N][N],f[M][N];//方案为i,[拿到]第j个奶酪走的距离最小值
double dist(PDD a,PDD b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y;//默认a[0]都是0
for(int i=0;i<=n;i++)//0到各奶酪的距离也要计算
for(int j=i+1;j<=n;j++){
w[i][j]=dist(a[i],a[j]);
w[j][i]=w[i][j];
}
memset(f,127,sizeof f);//浮点数设置最大值
double ans=f[0][0];//取最大值
for(int i=1;i<=n;i++) f[1<<(i-1)][i]=w[0][i];//初始化每个奶酪开始的起始值为0到该点距离
for(int i=0;i<1<<n;i++)
for(int j=1;j<=n;j++)
if(i>>(j-1)&1){//方案中含第j各奶酪
for(int k=1;k<=n;k++)//j前一步选的奶酪
if(i>>(k-1)&1)
f[i][j]=min(f[i][j],f[i-(1<<(j-1))][k]+w[k][j]);
}
for(int i=1;i<=n;i++) ans=min(ans,f[(1<<n)-1][i]);
printf("%.2f",ans);
return 0;
}
Acwing 291. 蒙德里安的梦想(参考夜林大佬)
求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。
例如当𝑁=2,𝑀=4 时,共有 5 种方案。当N=2,𝑀=3 时,共有 3 种方案。
如下图所示:
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数 N和 M。
当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。
输出格式
每个测试用例输出一个结果,每个结果占一行。
数据范围
1≤N,M≤11
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
f[i,j]表示走到第i列,由前面i-1列横放突出的方块在i列上产生的状态
并不是第i列的状态,但是第i列要基于这个状态进行放置方块
其次要判断该方案是否合法,首先横放导致剩余一列为空的连续方块数必须是偶数才能竖着放1*2的方块,否则该方案不合法,用一个st表来存储该方案是否合法
接着枚举i-1列放置的状态(j)和第i-2列放置的方块状态(k),二者不能重合也就是j&k==0
且更据前面的st表,st[j|k],也就是第i-1列真正的状态要满足st状态==true
接着状态转移就是f[i][j]+=f[i-1][k]由i-2列求得i-1列放置的方案数
最后枚举0-m列(一共0-m-1列),第m列放置方案为0(没有横放方块突出)的方案数就是结果
#include <iostream>
#include <cstring>
using namespace std;
const int N=12,M=1<<11;//摆放的小方格方案数 等价于 横着摆放的小方格方案数
long long f[N][M];//第i列在i-1列顶出的方块状态j所放置的方案数,过大会爆int
bool st[M];
int main(){
int n,m;
while(cin>>n>>m,n||m){
memset(f,0,sizeof f);
for(int i=0;i<1<<n;i++){//初始化st表,去除不合法(连续空格为奇数)的列
st[i]=true;
int cnt=0;//存空格的个数
for(int j=0;j<n;j++){
if(i>>j&1){
if(cnt&1){
st[i]=false;
break;
}
cnt=0;//既然该位是1,并且前面不是奇数个0(经过上面的if判断),计数器清零
}else cnt++;
}
if(cnt&1) st[i]=false;//最后一次
}
f[0][0]=1;//第0列第0行没有方块延申出来,方案数是1
for(int i=1;i<=m;i++)
for(int j=0;j<1<<n;j++)//第i-1列伸出到第i列的状态
for(int k=0;k<1<<n;k++)//第i-2列状态伸出到i-1的状态
if((j&k)==0&&st[j|k])//判断是否合法
f[i][j]+=f[i-1][k];
cout<<f[m][0]<<endl;
}
return 0;
}