HDU 1565&1569 方格取数系列(状压DP或者最大流)

方格取数(2)

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 6206    Accepted Submission(s): 1975

Problem Description

给你一个m*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取数所在的2个格子不能相邻,并且取出的数的和最大。

Input

包括多个测试实例,每个测试实例包括2整数m,n和m*n个非负数(m<=50,n<=50)

Output

对于每个测试实例,输出可能取得的最大的和

Sample Input

3 3

75 15 21

75 15 28

34 70 5

Sample Output

188

第一题数据范围较小,题目链接:HDU 1565,范围只有20,直接状压DP就可以水过了,把状态存下来可以优化一下空间,dp[i][j]表示当前第i行为第j个状态时的最大值,则有:

$dp[i][k]=max(dp[i][k], dp[i-1][j]+sta_val(k)] (sta[k]&sta[j]==0)$

第一题代码:

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define CLR(arr,val) memset(arr,val,sizeof(arr))
#define LC(x) (x<<1)
#define RC(x) ((x<<1)+1)
#define MID(x,y) ((x+y)>>1)
typedef pair<int,int> pii;
typedef long long LL;
const double PI=acos(-1.0);
const int N=21;
const int M=17720;
int pos[N][N];
int dp[N][M];
int sta[M],cnt;

inline bool check(const int &x,const int &y)
{
    return (x&y)==0;
}
inline int bittonum(const bitset<N> &t,const int &l,const int &n)
{
    int r=0;
    for (int i=0; i<n; ++i)
        if(t[i])
            r+=pos[l][i];
    return r;
}
void init(int n)
{
    int R=1<<n;
    cnt=0;
    for (int i=0; i<R; ++i)
        if(check(i,i<<1))
            sta[cnt++]=i;
    CLR(dp,0);
}
int main(void)
{
    int n,i,j,k,temp;
    while (~scanf("%d",&n))
    {
        if(!n)
        {
            puts("0");
            continue;
        }
        init(n);
        for (i=0; i<n; ++i)
            for (j=0; j<n; ++j)
                scanf("%d",&pos[i][j]);

        bitset<N>bit;

        for (i=0; i<cnt; ++i)
            dp[0][i]=bittonum(bit=sta[i],0,n);

        for (i=1; i<n; ++i)
        {
            for (j=0; j<cnt; ++j)
            {
                for (k=0; k<cnt; ++k)
                {
                    if(check(sta[j],sta[k]))
                    {
                        temp=dp[i-1][j]+bittonum(bit=sta[k],i,n);
                        if(temp>dp[i][k])
                            dp[i][k]=temp;
                    }
                }
            }
        }
        int r=0;
        for (i=0; i<cnt; ++i)
            if(dp[n-1][i]>r)
                r=dp[n-1][i];
        printf("%d\n",r);
    }
    return 0;
}

 

第二题范围较大$2^{50}$次的状态数预处理存的时候估计就T了,网上查了一下,发现是用最大流过的最近又学了最大流,先套个模版过掉再说……这个写的比较简单,应该是最朴素的最大流Ford-Fulkerson算法的DFS版,比较好理解一直DFS直到找不到可增广的路径。

建图要注意一下把图建成二分图的时候不是简单地吧奇数归一边,偶数归另外一边(DEBUG很久才发现这个低级错误),而是选取开头一个作为黑色,不相邻的作为白色,然后进行黑白染色,怎么判断当前的格子是黑色还是白色呢?看(i+j)的值,具体看下标从0还是1开始,简单点直接选第一个格子作为黑色即可,建图三条规则:假设原点S为0,汇点T为n*m+1。遇到黑色格子则建一条S->id[i][j]的边,流量为val[i][j],另外向旁边的合法白色格子建一条边,流量为INF,白色则是id[i][j]->T的边,流量也是val[i][j],既然是最大流显然都要多建一条初始容量为0的反向边。最后求S~T的最大流就是最大点权独立集的权值,用sum减去这个数就是答案了,具体定理和证明可以百度

代码:

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define CLR(arr,val) memset(arr,val,sizeof(arr))
#define LC(x) (x<<1)
#define RC(x) ((x<<1)+1)
#define MID(x,y) ((x+y)>>1)
typedef pair<int,int> pii;
typedef long long LL;
const double PI=acos(-1.0);
const int N=55;
const int dir[4][2]={{1,0},{-1,0},{0,-1},{0,1}};
struct edge
{
    int to;
    int nxt;
    int cap;
};
int val[N][N],id[N][N];
edge E[N*N*12];
int head[N*N],tot;
bitset<N*N>vis;

inline void bid_add(int s,int t,int c)
{
    E[tot].cap=c;
    E[tot].to=t;
    E[tot].nxt=head[s];
    head[s]=tot++;

    E[tot].cap=0;
    E[tot].to=s;
    E[tot].nxt=head[t];
    head[t]=tot++;
}
void init()
{
    CLR(head,-1);
    tot=0;
}
int dfs(int s,int t,int f)
{
    if(s==t)
        return f;
    vis[s]=1;
    for (int i=head[s]; ~i; i=E[i].nxt)
    {
        int v=E[i].to;
        if(!vis[v]&&E[i].cap>0)
        {
            int d=dfs(v,t,min<int>(f,E[i].cap));
            if(d>0)
            {
                E[i].cap-=d;
                E[i^1].cap+=d;
                return d;
            }
        }
    }
    return 0;
}
int max_flow(int s,int t)
{
    int r=0;
    while (true)
    {
        vis.reset();
        int f=dfs(s,t,INF);
        if(!f)
            break;
        r+=f;
    }
    return r;
}
int main(void)
{
    int n,m,i,j,k;
    while (~scanf("%d%d",&n,&m))
    {
        init();
        int sum=0;
        for (i=0; i<n; ++i)
        {
            for (j=0; j<m; ++j)
            {
                scanf("%d",&val[i][j]);
                sum+=val[i][j];
                id[i][j]=i*m+j+1;
            }
        }
        int S=0,T=n*m+1;
        for (i=0; i<n; ++i)
        {
            for (j=0; j<m; ++j)
            {
                if((i+j)&1)
                    bid_add(id[i][j],T,val[i][j]);
                else
                {
                    bid_add(S,id[i][j],val[i][j]);
                    for (k=0; k<4; ++k)
                    {
                        int ti=i+dir[k][0];
                        int tj=j+dir[k][1];
                        if(ti>=0&&ti<n&&tj>=0&&tj<m)
                            bid_add(id[i][j],id[ti][tj],INF);
                    }
                }
            }
        }
        printf("%d\n",sum-max_flow(S,T));
    }
    return 0;
}
时间: 2024-10-03 22:53:39

HDU 1565&1569 方格取数系列(状压DP或者最大流)的相关文章

HDU 1565 1569 方格取数(最大点权独立集)

HDU 1565 1569 方格取数(最大点权独立集) 题目链接 题意:中文题 思路:最大点权独立集 = 总权值 - 最小割 = 总权值 - 最大流 那么原图周围不能连边,那么就能够分成黑白棋盘.源点连向黑点.白点连向汇点,容量都为点容量.然后黑白之间相邻的就连一条容量无限大的边 代码: #include <cstdio> #include <cstring> #include <queue> #include <algorithm> using names

HDU 1565 方格取数(1)(状压dp)

感觉这道题目的数据比较水啊,程序的时间复杂度为1711^2*20竟然也可以过掉....其他的就是状压了啊,注意需要滚动一下啊.... 方格取数(1) Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 5701    Accepted Submission(s): 2159 Problem Description 给你一个n*n的格子的棋

hdu 2167 方格取数 【状压dp】(经典)

<题目链接> 题目大意: 给出一些数字组成的n*n阶矩阵,这些数字都在[10,99]内,并且这个矩阵的  3<=n<=15,从这个矩阵中随机取出一些数字,在取完某个数字后,该数字周围8个点都不能取,问:取得数字的最大和为多少? 解题分析: 由于对每一个数,有选和不选两种可能,分别对应状态压缩中的1和0,且 n<=15,1<<15不是非常大,因此就可以非常自然的想到状态压缩. 此题要与普通的状压dp不同的是,当某一行取某种方案时,如何求出这种取数的所有取得的数之和,

HDU 1565:方格取数(1)(最大点权独立集)***

http://acm.hdu.edu.cn/showproblem.php?pid=1565 题意:中文. 思路:一个棋盘,要使得相邻的点不能同时选,问最大和是多少,这个问题就是最大点权独立集. 可以转化为所有的点权 - 最小点权覆盖集(最小割) = 最大点权独立集. 转载两个的定义:这里. 覆盖集(vertex covering set,VCS)是无向图的一个点集,使得该图中所有边都至少有一个端点在该集合内.形式化的定义是点覆盖集为G'VV∈(,)uvE∀∈,满足对于,都有 或成立,即,'uV

【网络流】hdu 1569 方格取数(2)

/* 和1565一样: 总点数的权 - 最小覆盖点集 = 最大独立集 -------------------------------------- void add(int u, int v, int f)加边 { e[ct].u = u; e[ct].v = v; e[ct].f = f; next[ct] = first[u]; first[u] = ct++; e[ct].u = v; e[ct].v = u; e[ct].f = 0; next[ct] = first[v]; first

hdu 1569 方格取数(2)再解

上次我说用STL超时了,而用数组为0ms,其实不然,这个题STL依然不超时,代码如下 #include<map> #include<set> #include<stack> #include<queue> #include<cmath> #include<vector> #include<cstdio> #include<string> #include<cstring> #include<c

hdoj 1569 方格取数 【最大点权独立集-&gt;最大流】

题目:hdoj 1569 方格取数 题意:中文题目,就不说题意了. 分类:最大流 |  dp 分析:dp的话应该是个数塔模型,不难做,这里讲转化为图的做法. 这个题目的关键在于转化为一个二分图,来求一个二分图的最大点权独立集,而最大点权独立集 = 点权和 - 最小点权覆盖 最小点权覆盖: 从x或者y集合中选取一些点,使这些点覆盖所有的边,并且选出来的点的权值尽可能小. 最大点权独立集:找到二分图中权值和最大的点集,然后让任意点没有边. 而最小点权覆盖 = 最小割 = 最大流 = sum - 最大

HDU 3376 &amp;&amp; 2686 方格取数 最大和 费用流裸题

题意: 1.一个人从[1,1] ->[n,n] ->[1,1] 2.只能走最短路 3.走过的点不能再走 问最大和. 对每个点拆点限流为1即可满足3. 费用流流量为2满足1 最大费用流,先给图取负,结果再取负,满足2 #include <stdio.h> #include <string.h> #include <iostream> #include <math.h> #include <queue> #include <set&

hdu 5135 Little Zu Chongzhi&#39;s Triangles 状压DP

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5135 从大到小贪心是不对的! 比如 4 30 40 50 89 这组数据 结果应该是600 如果取后三条边只会是一个细长细长的三角形根本没多大面积 只不过因为数据水然后贪心碰巧能过而已 看到网上题解很多人用贪心我好郁闷... 状压DP 看到边的数目很少就应该想到这个了 先枚举可能出现的所有三角形 记录他们使用了那些边还有他们的面积 然后再枚举状态之中 套一层遍历所有三角形的循环 如果当前状态和三角形