状压 dp

在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;

const int N=32;
const int M=15;
int dp[N][1<<M];
//dp[i][j]表示前i行已经处理好,且第i行状态为j(j以二进制的形式处理)所能带走的最大宠物数
//对于状态j,二进制位为1表示该位放了宠物,为0表示该位没有放宠物 
int rows[N];//存储每一行的状态(化为二进制) 
bool valid[1<<M];//valid[i]表示i是否是合法状态(二进制位是否有相邻的1),无则为true 

int cnt_1(int x)//求x的二进制中有几个1 
{
    int cnt=0;
    while(x)
    {
        if(x&1)cnt++;
        x=x>>1;
    }
    return cnt;
}

int main()
{
    int n,m;
    cin>>n>>m;
    int upper_limit=(1<<m)-1;//每行的状态的上限:11...111(m个1)
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            int tmp;
            cin>>tmp;
            if(tmp)rows[i]|=1<<(j-1);//将输入的每一行转化为二进制数
            //如输入 0 1 0 1,则rows[i]=10    
        }    
    } 
    
    for(int i=0;i<=upper_limit;i++)//预处理valid数组,验证每一种状态(00...000~11...111)是否合法 
    { 
        if((i&(i>>1))==0)valid[i]=true;//没有相邻的1
        else valid[i]=false;//有相邻的1 
    }
    
    for(int i=1;i<=n;i++)//枚举每一行i 
    {
        for(int j=0;j<=upper_limit;j++)//枚举该行的每一种状态j 
        {
            //valid[j]==true表示状态j本身是一种合法的状态
            //rows[i]&j==0表示第i行的状态可以是j,即放宠物的位置没有占用食物的位置,
            //若row[i]的某一位为1,已经放了食物,那么j的对应位必为0,即一定不能放宠物 
            if((rows[i]&j)==0&&valid[j])
            {
                for(int k=0;k<=upper_limit;k++)//枚举前i-1行所有可能的状态k 
                {
                    //valid[k]==true,表示k本身是一个合法的状态 
                    //j&k==0,表示前一行与当前行的任意一列都不会出现两个宠物
                    //两个条件均满足,说明从k状态可以转移到j 
                    if((j&k)==0&&valid[k])
                    {
                        //累加第i-1行状态为k的所有方案再加上状态j中"1"的个数(第i行可以放宠物的位置)
                        //取其中的最大值更新dp[i][j] 
                        dp[i][j]=max(dp[i][j],dp[i-1][k]+cnt_1(j));
                    }
                }
            }
        }
    }
    
    int ans=0;
    for(int i=0;i<=upper_limit;i++)//枚举第i行所有可能的状态00...000~11...111 
    {
        ans=max(ans,dp[n][i]);//比较得出最大值 
     } 
     cout<<ans<<endl;
    return 0;
}

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
#define int long long

using namespace std;

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,z;
    cin>>n>>z;
    vector<int>x(n),y(n);
    for(int i=0;i<n;++i) cin>>x[i];
    for(int i=0;i<n;++i) cin>>y[i];
    int st=0,ed=0;
    for(int i=0;i<n;++i){
        int t;cin>>t;
        st|=t<<i;
    }
    for(int i=0;i<n;++i){
        int t;cin>>t;
        ed|=t<<i;
    }
    vector<int>vis((1<<n),0),dis((1<<n),1e18);
    priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
    dis[st]=0;
    q.push({0,st});
    while(!q.empty()){
        auto [l,r]=q.top();q.pop();
        if(vis[r]) continue;
        vis[r]=1;
        for(int i=0;i<n;++i){
            if((r>>i)&1){
                if(l+y[i]<dis[r^(1<<i)]){
                    dis[r^(1<<i)]=l+y[i];
                    q.push({dis[r^(1<<i)],r^(1<<i)});
                }
            }
            else{
                if(l+x[i]<dis[r^(1<<i)]){
                    dis[r^(1<<i)]=l+x[i];
                    q.push({dis[r^(1<<i)],r^(1<<i)});
                }
            }
        }
        for(int i=0;i<n;++i)
            for(int j=i+1;j<n;++j){
                if(((r>>i)^(r>>j))&1){
                    if(l+z<dis[r^(1<<i)^(1<<j)]){
                        dis[r^(1<<i)^(1<<j)]=l+z;
                        q.push({dis[r^(1<<i)^(1<<j)],r^(1<<i)^(1<<j)});
                    }
                }
            }
    }
    cout<<dis[ed]<<endl;
}

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int main() 
{
    int n;
    cin >> n;
    vector<string> word(n);
    vector<int> first(n), last(n);
    vector<vector<int>> dp(1 << n, vector<int>(26,0));
    for (int i = 0; i < n; i++) {
        cin >> word[i];
        first[i] = word[i][0] - 'a';
        last[i] = word[i].back()-'a';
    }
    for (int bits = 0; bits < (1 << n); bits++) {
        for (int i = 0; i < n; i++) {
            if (bits & (1 << i)) {
                dp[bits][last[i]] = max(dp[bits][last[i]], dp[bits ^ (1 << i)][first[i]] + 1);
            }
        }
    }
    int res = 0;
    for (int i = 0; i < 26; i++) {
        res = max(res, dp[(1 << n) - 1][i]);
    }
    cout << res << endl;
    return 0;
}

P10422 [蓝桥杯 2023 国 A] 迷宫探险【暂无数据】

题目描述

勇士小蓝准备去遥远的 LQ 迷宫探险,拿到迷宫中的宝藏。迷宫可以看做是一个具有 NNN 个顶点(顶点编号为 0∼N−10\sim N-10N1) 和 MMM 条边的无向图,其中每个顶点上都有一只怪物,每只怪物都具有一定的攻击力,每条边都具有一个权重 www 表示小蓝经过边时消耗的时间。

想要拿到迷宫宝藏,小蓝需要从 000 号顶点出发对地图进行探险,在经过顶点时可以对怪物进行击杀,小蓝具有必杀技,能保证一招就击败怪物,但在小蓝对某个怪物进行击杀时,与这个怪物所在结点相邻接的结点上仍存活着的怪物会对小蓝发起一次攻击(注意,不包括小蓝正在击杀的怪物),小蓝会减少对应攻击力大小的血量。当小蓝击杀完所有怪物并且到达顶点 N−1N-1N1,并且此时小蓝的血量大于 000,那么小蓝才可以获得迷宫宝藏。

注意,小蓝的必杀技很快,因此在击杀怪物时可以视为不消耗时间;一个怪物只需要被击杀一次就会消失,只有在小蓝击杀怪物时,与其相邻接的结点上的怪物才会对小蓝发起一次攻击。

如果小蓝可以获得迷宫宝藏,请你输出所需要的最小时间。否则输出 −1-11 即可。

输入格式

输入的第一行包含三个整数 N,M,HPN, M, HPN,M,HP,相邻的整数之间使用一个空格分隔,分别表示顶点数、无向边数以及小蓝初始时侯的血量。

第二行包含 NNN 个整数,相邻的整数之间使用一个空格分隔,其中第 i(0≤i≤N−1)i(0\le i\le N-1)i(0iN1) 个整数表示编号为 iii 的顶点上的怪物的攻击力大小;

接下来 MMM 行,每行包含三个整数 u,v,wu, v, wu,v,w 表示在顶点 uuuvvv 之间存在一条权重为 www 的无向边。

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;

const int N = 15;  // 最大节点数
int n, m, hpp;     // n:节点数, m:边数, hpp:初始生命值
int hurt[N];       // 每个节点的伤害值
int Time[N][1 << N][101];  // 状态数组:Time[位置][状态][生命值] = 时间
struct s { int v, w; };    // 边的结构体:v是相邻节点,w是边权
struct ss { 
    int x,        // 当前位置
        sta,       // 当前状态(二进制位表示哪些节点未被访问)
        hp,        // 当前生命值
        t;         // 当前总时间
};
vector<s> e[N];    // 图的邻接表表示

// 优先队列的比较函数(按时间升序)
struct cmp {
    bool operator()(ss a, ss b)const {
        return a.t > b.t;
    }
};

priority_queue<ss, vector<ss>, cmp> q;  // 优先队列

int main() {
    memset(Time, 0x3f, sizeof Time);  // 初始化时间为无穷大
    cin >> n >> m >> hpp;
    
    // 读取每个节点的伤害值
    for (int i = 0; i < n; i++) cin >> hurt[i];
    
    int st = (1 << n) - 1;  // 初始状态:所有节点都未被访问(二进制全1)
    
    // 读取图的边信息
    for (int i = 1; i <= m; i++) {
        int u, v, w; cin >> u >> v >> w;
        e[u].push_back({ v,w });
        e[v].push_back({ u,w });
    }
    
    // 初始状态入队:位置0,全未访问状态,满生命值,时间0
    q.push({ 0,st,hpp,0 });
    Time[0][st][hpp] = 0;
    
    while (q.size()) {
        auto tmp = q.top();
        q.pop();
        int x = tmp.x, sta = tmp.sta, hp = tmp.hp, t = tmp.t;
        
        // 如果到达终点且所有节点都已访问
        if (sta == 0 && x == n - 1) {
            cout << t;
            return 0;
        }
        
        // 如果当前状态不是最优,跳过
        if (t > Time[x][sta][hp]) continue;
        
        // 遍历所有邻接边
        for (auto i : e[x]) {
            int v = i.v, w = i.w;
            
            // 如果新状态更优,则更新
            if (Time[v][sta][hp] <= t + w) continue;
            Time[v][sta][hp] = t + w;
            q.push({ v,sta,hp,t + w });
        }
        
        // 如果当前位置未被访问过(sta的第x位为1)
        if (sta >> x & 1) {
            int hhp = hp;
            
            // 计算访问该节点后的生命值:减去所有未访问邻接节点的伤害
            for (auto i : e[x]) 
                if (sta >> i.v & 1) 
                    hhp -= hurt[i.v];
            
            // 如果生命值仍大于0,则更新状态
            if (hhp > 0 && Time[x][sta & ~(1 << x)][hhp] > t) {
                Time[x][sta & ~(1 << x)][hhp] = t;
                q.push({ x,sta & ~(1 << x),hhp,t });
            }
        }
    }
    
    // 如果没有找到解,输出-1
    cout << -1;
    return 0;
}

问题描述

魔法师小蓝为了营救自己的朋友小Q,来到了敌人布置的魔法阵。魔法阵可以看作是一幅具有N个结点M条边的无向图,结点编号为0,1,2,…,N−1,图中没有重边和自环。敌人在每条边上都布置了陷阱,每条边都有一个伤害属性w,每当小蓝经过一条边时就会受到这条边对应的w的伤害。小蓝从结点0出发,沿着边行走,想要到达结点N−1营救小Q。

小蓝有一种特殊的魔法可以使用,假设一条路径按照顺序依次经过了以下L条边:e₁,e₂,…,eL(可以出现重复的边),那么期间小蓝受到的总伤害就是P=∑w(ei)。如果L≥K,那么小蓝就可以从这L条边当中选出连续出现的K条边e_c,e_{c+1},…,e_{c+K−1}并免去在这K条边行走期间所受到的伤害,即使用魔法之后路径总伤害变为P’=P−∑w(ei)。注意必须恰好选出连续出现的K条边,所以当L<K时无法使用魔法。

小蓝最多只可以使用一次上述的魔法,请问从结点0出发到结点N−1受到的最小伤害是多少?题目保证至少存在一条从结点0到N−1的路径。

输入格式

第一行输入三个整数N,K,M,用空格分隔。接下来M行,每行包含三个整数u,v,w,表示结点u与结点v之间存在一条伤害属性为w的无向边。

输出格式

输出一行,包含一个整数,表示小蓝从结点0到结点N−1受到的最小伤害。

代码实现

#include <bits/stdc++.h>

using namespace std;

const int N=1e3+10;

struct Edge // 边类 
{
    int to;    // 另一个端点 
    int weight; // 边权(伤害值) 
    Edge(int t=0,int w=0):to(t),weight(w){}    
};

int dp[N][N]; // dp[i][j]表示从0走到i,消去最后j条边的伤害值,最终受到的伤害 
vector<Edge> edge[N]; // 存储所有边的信息 
int n,k,m; // n个结点,m对无向边,消去的边数k 

void dijkstra()
{
    queue<int> q; // 存储结点编号的队列 
    q.push(0);    // 起点入队 
    
    while(!q.empty())
    {
        int head=q.front(); q.pop(); // 取出队首head 
        for(int i=0;i<edge[head].size();i++) // 遍历队首结点的所有边 
        {
            int to=edge[head][i].to, weight=edge[head][i].weight; // 取出另一端点和权值 
            bool tag=0; // tag=1表示起点到该端点的距离发生了更新(缩小),需将其入队进一步松弛 
            
            // 情况1:不使用魔法,直接累加伤害
            if(dp[to][0]>dp[head][0]+weight) 
            {
                dp[to][0]=dp[head][0]+weight;
                tag=1;    
            }    
            
            // 情况2:使用魔法,消去最后1~k条边
            for(int j=1;j<=k;j++) 
            {
                if(dp[to][j]>dp[head][j-1]) // 消去最后j条边
                {
                    dp[to][j]=dp[head][j-1];
                    tag=1;
                }
            }
            
            // 情况3:已经消去了k条边,不能再消去
            if(dp[to][k]>dp[head][k]+weight) 
            {
                dp[to][k]=dp[head][k]+weight;
                tag=1;
            }
            
            if(tag) q.push(to); // 发生了更新,该结点入队进行后续的松弛操作 
        }
    }    
}

int main()
{
    scanf("%d%d%d",&n,&k,&m);
    memset(dp,0x3f,sizeof(dp)); // dp数组初始化为极大值 
    dp[0][0]=0; // 从0走到0,消去0条边,受到的伤害为0 
    
    // 输入m条无向边 
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        edge[u].push_back(Edge(v,w));
        edge[v].push_back(Edge(u,w));    
    }    
    
    dijkstra(); // 跑一遍dijkstra算法 
    
    // 输出结果:比较不使用魔法和使用魔法的情况
    printf("%d\n",min(dp[n-1][0],dp[n-1][k]));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值