今日总结2024/5/18

今日学习了状态压缩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 种方案。

如下图所示:

2411_1.jpg

输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值