在百度之星的复赛上对这道题没什么思路,后来看到题解说这是一道状压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;
}
 
                   
                   
                   
                   
                             
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
              
             
                   630
					630
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
            


 
            