状态压缩DP题目小节(二)

最近做的状态压缩DP小节:


http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4257

zoj 4257

一堆气体相互碰撞产生能量,求最后能产生的最大能量,应该是很基础的状态压缩DP吧,设dp[flag]表示状态flag时能产生的最大能量,(flag中1表示该气体还存在,0表示该气体已经消失)边界条件是flag所有位都为一时,这时产生的能量为0,然后就枚举最后剩下的气体,求最大值即可。

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
int dp[1<<10];
int bo[11][11];
int n;
int max(int a,int b)
{
    return a>b?a:b;
}
int dfs(int flag)
{
    if(dp[flag]!=-1)
    return dp[flag];
    int i,j;
    int ans=0;
    for(i=1;i<=n;i++)
    {
        if(flag&1<<(i-1))
        {
            for(j=1;j<=n;j++)
            {
                if(j!=i&&(flag&1<<(j-1))==0)
                {
                    ans=max(ans,dfs(flag^(1<<(j-1)))+bo[i][j]);
                }
            }
        }
    }
    return dp[flag]=ans;
}
int main()
{
    //freopen("dd.txt","r",stdin);
    while(scanf("%d",&n)&&n)
    {
        int i,j;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            scanf("%d",&bo[i][j]);
        }
        memset(dp,-1,sizeof(dp));
        int ans=0;
        dp[(1<<n)-1]=0;
        for(i=0;i<n;i++)
        {
            ans=max(ans,dfs(1<<i));
        }
        printf("%d\n",ans);
    }
    return 0;
}


http://acm.hdu.edu.cn/showproblem.php?pid=3001

hdu 3001:

类似于TSP问题,但是这里不同的是每个点最多可以走两次,而不是一次,所以我们可以用三进制来表示每一个状态,我们设dp[now][flag]表示目前在now点状态为flag时的最小花费,则边界条件为当(1<<(now-1))==flag时,这时表示起点在now点,还没开始走,则此时的最小花费是0,还有一个边界条件为(1<<(now-1))*2==flag,这表示从now点走到now点,这是不可能的,所以把最小花费设为无穷大,剩下的就是状态转移了。

dp[now][flag]=dp[pre][flag']+map[pre][now],满足以下几个条件:

1:pre!=now且map[pre][now]不为-1(也就是存在边)

2:flag的第pre位不为0

3:flag'的第now位比flag的第now位少1.

我们求最小值即可,我们要枚举所有每一位都不为0的状态,取它们的最小值即为答案。

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#define inf 2100000000
using namespace std;
int bo[11][11];
int dp[11][60000];
int bit[60000][11];
int pow[12];
void init()
{
    int i,j;
    for(i=0;i<=59048;i++)
    {
        int t=i;
        for(j=1;j<=10;j++)
        {
            bit[i][j]=t%3;
            t/=3;
        }
    }
    pow[1]=1;
    for(i=2;i<=11;i++)
    pow[i]=pow[i-1]*3;
}
int check(int x)
{
    while(x)
    {
        if(x%3==0)
        return 0;
        x/=3;
    }
    return 1;
}
int n,m;
int min(int a,int b)
{
    return a<b?a:b;
}
int dfs(int now,int flag)
{
    if(dp[now][flag]!=-1)
    return dp[now][flag];
    if(pow[now]==flag)
    return dp[now][flag]=0;
    if(pow[now]*2==flag)
    return dp[now][flag]=inf;
    int ans=inf,i;
    for(i=1;i<=n;i++)
    {
        if(i!=now&&bit[flag][i]&&bo[i][now]!=-1)
        {
            int tmp=dfs(i,flag-pow[now]);
            if(tmp!=inf)
            {
                ans=min(ans,tmp+bo[i][now]);
            }
        }
    }
    return dp[now][flag]=ans;
}
int main()
{
   // freopen("dd.txt","r",stdin);
    init();
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        int i,a,b,c;
        memset(bo,-1,sizeof(bo));
        memset(dp,-1,sizeof(dp));
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            if(bo[a][b]==-1||bo[a][b]>c)
            bo[a][b]=bo[b][a]=c;
        }
        int ma=pow[n+1]-1,mi=ma/2;
        int ans=inf;
        for(i=1;i<=n;i++)
        {
            for(int j=mi;j<=ma;j++)
            {
                if(check(j))
                ans=min(ans,dfs(i,j));
            }
        }
        if(ans==inf)
        ans=-1;
       printf("%d\n",ans);
    }
    return 0;
}


http://poj.org/problem?id=3311
poj 3311

也是类似于TSP问题,不过这个时候每个点可以走无限次,所以我们要用依次floyd算法求一下个点之间的最短路,这里设dist[u][v]表示u和v之间的最短路,然后就和平常求TSP问题一样了,我们还是设dp[now][flag]表示当前在now点状态为flag的最小花费,边界条件为flag==(1<<(now-1))时,此时表示从起点开始走第一次到达到达now,为其他店还没开hi走,则dp[now][flag]=dist[0][now],其他情况基本和上体一样。

注意题目要求最后一定要回到0点。

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
#define inf 2100000000
using namespace std;
int map[11][11];
int dp[11][1<<10];
int n;
int min(int a,int b)
{
    return a<b?a:b;
}
int dfs(int now,int flag)
{
    //printf("%d %d\n",now, flag);
    if(dp[now][flag]!=-1)
    return dp[now][flag];
    int i,j;
    if(flag==(1<<(now-1)))
    return dp[now][flag]=map[0][now];
    int ans=inf;
    if(now==0)
    {
        for(i=1;i<=n;i++)
        {
            ans=min(ans,dfs(i,flag)+map[i][now]);
        }
    }
    else
    {
        for(i=1;i<=n;i++)
        {
            if(i!=now&&flag&(1<<(i-1)))
            {
                ans=min(ans,map[i][now]+dfs(i,flag^(1<<(now-1))));
            }
        }
    }
    return dp[now][flag]=ans;
}
void floyd(int n)
{
    int i,j,k;
    for(i=0;i<=n;i++)
    {
        for(j=0;j<=n;j++)
        {
            for(k=0;k<=n;k++)
            {
                map[j][k]=min(map[j][k],map[j][i]+map[i][k]);
            }
        }
    }
}
int main()
{
    //freopen("dd.txt","r",stdin);
    int i,j;
    while(scanf("%d",&n)&&n)
    {
        for(i=0;i<=n;i++)
        {
            for(j=0;j<=n;j++)
            scanf("%d",&map[i][j]);
        }
        floyd(n);
        memset(dp,-1,sizeof(dp));
        dp[0][0]=0;
        printf("%d\n",dfs(0,(1<<n)-1));
    }
    return 0;
}


poj 2288

还是类似于TSP问题,不过这里对于花费做了新的定义,花费分为三部分

1:路径中每个点的权值之和。

2:路径中相邻两个点的权值之积的和。

3:若存在路径pi->pi+1->pi+2,且pi和pi+2之间有边,则花费还要加上这三点的权值之和。

我们要求花费最大的路径以及这样的路径的个数

所以我们社状态时不能只考虑当前点,还要考虑之前的点了,所以我们设dp[now][pre][flag]表示当前点在now,上一个点在pre,此时状态为flag时的最大花费。设way[now][flag][flag]为dp[now][pre][flag]取最大值时的路径条数。则接下来我们就和求TSP问题时差不多了,只是求花费时稍微麻烦一点,边界条件是当now为起点时,即(1<<(now-1))==flag时,此时花费为now点的权值,此时的路径条数为1.代码还有一些细节,具体实现请参考代码。

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
#define maxn 100010
#define ll long long
using namespace std;
ll dp[14][14][1<<13];
ll way[14][14][1<<13];
int bo[14][14];
int v[14];
void init()
{
    memset(bo,0,sizeof(bo));
    memset(way,0,sizeof(way));
    memset(dp,-1,sizeof(dp));
}
ll max(ll a,ll b)
{
    return a>b?a:b;
}
int n;
ll dfs(int now,int pre,int flag)
{
   // printf("f");
    if(dp[now][pre][flag]!=-1)
    return dp[now][pre][flag];
    if(1<<(now-1)==flag)
    {
        way[now][pre][flag]=1;
        return dp[now][pre][flag]=v[now];
    }
    int i,tru=0;
    ll ans=0;
    for(i=1;i<=n;i++)
    {
        if(i!=now&&i!=pre&&(flag&(1<<(i-1))))
        {
            tru=1;
            if(bo[i][pre])
            {
                ll tmp=0;
                tmp=dfs(pre,i,flag^(1<<(now-1)));
                if(tmp)
                {
                    tmp+=v[now]+v[now]*v[pre];
                    if(bo[i][now])
                    tmp+=v[now]*v[pre]*v[i];
                    ans=max(ans,tmp);
                }
            }
        }
    }
    if(!tru)
    {
        dp[now][pre][flag]=dfs(pre,0,flag^(1<<(now-1)))+v[now]+v[now]*v[pre];
        way[now][pre][flag]=1;
        return dp[now][pre][flag];
    }
    if(ans)
    {
        for(i=1;i<=n;i++)
        {
            if(i!=now&&i!=pre&&bo[i][pre]&&(flag&(1<<(i-1))))
            {
                ll tmp=0;
                tmp=dfs(pre,i,flag^(1<<(now-1)));
                if(tmp)
                {
                    tmp+=v[now]+v[now]*v[pre];
                    if(bo[i][now])
                    tmp+=v[now]*v[pre]*v[i];
                    if(ans==tmp)
                    {
                        way[now][pre][flag]+=way[pre][i][flag^(1<<(now-1))];
                    }
                }
            }
        }
    }
    return dp[now][pre][flag]=ans;
}
int main()
{
    //freopen("dd.txt","r",stdin);
    int ncase;
    scanf("%d",&ncase);
    while(ncase--)
    {
        int m,i,a,b,j;
        init();
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
        scanf("%d",&v[i]);
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            bo[a][b]=bo[b][a]=1;
        }
        ll ans=0,num=0;
        if(n==1)
        printf("%d 1\n",v[1]);
        else
        {
            for(i=1;i<=n;i++)
            {
                for(j=1;j<=n;j++)
                {
                    if(i!=j&&bo[i][j])
                    {
                        dfs(i,j,(1<<n)-1);
                        ans=max(ans,dp[i][j][(1<<n)-1]);
                    }
                }
            }
            if(ans==0)
            printf("0 0\n");
            else
            {
                for(i=1;i<=n;i++)
                {
                    for(j=1;j<=n;j++)
                    {
                        if(i!=j&&bo[i][j]&&dp[i][j][(1<<n)-1]==ans)
                        {
                            num+=way[i][j][(1<<n)-1];
                        }
                    }
                }
                printf("%I64d %I64d\n",ans,num/2);
            }
        }
    }
    return 0;
}



原文地址:https://www.cnblogs.com/xinyuyuanm/p/2999158.html