【HDU6149】Valley Numer II(状压DP)

在百度之星的复赛上对这道题没什么思路,后来看到题解说这是一道状压DP,正好之前对于状压DP知识粗略的了解过一点,借这道题正好好好学习一下状压DP。

 

这是我学习状压DP看的一些资料:

https://wenku.baidu.com/view/84164e1a227916888486d7d6.html

https://wenku.baidu.com/view/8b3ffc6314791711cc7917a7.html?re=view

 

题意:在一个图上有N个点,有的点被标记位高点,有的点被标记位低点,然后图中有M条边,问这个图最多可以找到多少个三元组<X,Y, Z>,其中X,Z是高点,Y是低点,并且XY,YZ之间有边,并且每一个点只能存在于一个三元组中(初看题意就是一道匹配题)

 

分析:因为说给的N的数据量为30,看起来不像状压,但是只要状压高点的状态即可,因为高点最多就只有15个,显然可以状压,然后怎么状态转移呢,可以一次选取低点P,对于每一个高点的状态X,X的每一位代表高点是否被选取,1为已经选取,0为未被选取,对于这样的一个X,如果P和两个高点相连,并且这两个高点没有被选取,那么就可以转移到选取这两个高点之后的状态并且数值加1

状态转移的过程为:

 

if(!(j >> pp & 1) && !(j >> pq & 1))
{
    int tmp = j | (1 << pp);
    tmp = tmp | (1 << pq);
    dp[i + 1][tmp] = max(dp[i + 1][tmp], dp[i][j] + 1);
    ans = max(ans, dp[i + 1][tmp]);
}

 

这是选取,当然也可以不选取那就保持X状态进入下一轮即可:

 

//这一轮可以什么都不选,这很重要,一开始没考虑到WA了2次
dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);
ans = max(ans, dp[i + 1][j]);


同时,将每一次更新的值与结果去最大值,这样就可以得到最大匹配数。

 

其实,分析下来,这种思路就是01背包的思路,可以换成01背包来写,背包的容量就是2^k - 1(每一个高点都被选取)

 

AC代码:

 

#include <iostream>
#include <cstring>
#include <cmath>
#include <queue>
#include <stack>
#include <list>
#include <map>
#include <set>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 16;
int dp[35][(1 << MAXN) + 10];
bool graph[35][35];
bool vertex[35];
int n, m, k;

int main()
{
    int CASE;
    scanf("%d", &CASE);
    while(CASE--)
    {
        memset(dp, 0, sizeof(dp));
        memset(graph, 0, sizeof(graph));
        memset(vertex, 0, sizeof(vertex));

        scanf("%d%d%d", &n, &m, &k);
        int u, v;
        for(int i = 0; i < m; ++i)
        {
            scanf("%d%d", &u, &v);
            graph[u][v] = graph[v][u] = true;
        }
        for(int i = 0; i < k; ++i)
        {
            scanf("%d", &v);
            vertex[v] = true;
        }
        int point[35] = {0}, lowpoint[35] = {0};
        int high = 0, low = 0;
        for(int i = 1; i <= n; ++i)
        {
            if(!vertex[i])
                lowpoint[low++] = i;
            else
                point[i] = high++;
        }
        int ans = 0;
        for(int i = 0; i < low; ++i)
        {
            u = lowpoint[i];
            for(int j = 0; j < (1 << k); ++j)
            {
                //这一轮可以什么都不选,这很重要,一开始没考虑到WA了2次
                dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);
                ans = max(ans, dp[i + 1][j]);
                for(int p = 1; p <= n; ++p)
                {
                    if(graph[u][p] && vertex[p])
                    {
                        for(int q = p + 1; q <= n; ++q)
                        {
                            if(graph[u][q] && vertex[q])
                            {
                                int pp = point[p];
                                int pq = point[q];
                                if(!(j >> pp & 1) && !(j >> pq & 1))
                                {
                                    int tmp = j | (1 << pp);
                                    tmp = tmp | (1 << pq);
                                    dp[i + 1][tmp] = max(dp[i + 1][tmp], dp[i][j] + 1);
                                    ans = max(ans, dp[i + 1][tmp]);
                                }
                            }
                        }
                    }
                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值