状态压缩DP 不断学习中。。。。。。

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

注意题目叙述: “But Mr Acmer gets bored so easily that he doesn't want to visit a city more than twice!”,

  题目大意:加限制的最小生成树,限制条件:每个节点最多只能到达两次, 用三进制(想想二进制表示的意思,可对比理解)

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=20;
const int ST=59049;
const int INF=0x3f3f3f3f;

int n,m;
int g[N][N],dp[ST][N],ans;

int bit[]={1,3,9,27,81,243,729,2187,6561,19683,59049}; //三进制初始化

int Cal(int st){    //判断n条路是否全都走过
    for(int i=0;i<n;i++){
        if(st%3==0)
            return 0;
        st/=3;
    }
    return 1;
}

void Solve(){
    int i,j,k;
    ans=INF;
    for(i=0;i<bit[n];i++)
        for(j=0;j<n;j++)
            dp[i][j]=INF;
    for(i=0;i<n;i++)        //起点都为0
        dp[bit[i]][i]=0;
    for(i=0;i<bit[n];i++)
        for(j=0;j<n;j++)
            if(dp[i][j]!=INF){
                int tmp=i;
                for(k=0;k<n;k++){
                    if(tmp%3<2)     //不超过两次
                        dp[i+bit[k]][k]=min(dp[i+bit[k]][k],dp[i][j]+g[j][k]);
                    tmp/=3;
                }
                if(Cal(i))  //如果n条路都走过了,
                    ans=min(ans,dp[i][j]);
            }
}

int main(){

    //freopen("input.txt","r",stdin);

    while(~scanf("%d%d",&n,&m)){
        memset(g,0x3f,sizeof(g));
        int u,v,w;
        while(m--){
            scanf("%d%d%d",&u,&v,&w);
            u--;v--;
            if(g[u][v]>w)
                g[u][v]=g[v][u]=w;
        }
        Solve();
        printf("%d\n",ans==INF?-1:ans);
    }
    return 0;
}
View Code

 

 

HDU  1074  doing homework :  http://acm.hdu.edu.cn/showproblem.php?pid=1074

思路:

这题有作业数的规定,有每一样作业的截止时间要求,有写每一样作业所需花费的时间,作业要按期完成,否则每超一天扣一分,问怎样才能让他扣分最少。

原先我想和其他dp一样找状态转移方程,但感觉状态要表示出来很困难。由于题目说作业数最多是15,因此若我们采用一个2的15次方的数来表示,每一个数为0则表示尚未完成此项作业,为1则表示已完成此项作业,这样这个状态就很好表示了。

在这里设置一个结构体,里面要包含一个记录前驱的pre,然后自然的有最小的扣分代价,此外,由于是否在规定时间内完成了这道题目是由当前时间,截止时间和你花费时间共同决定的,那你就必须判断当前时间与截止时间的差距是多少,因此外设一个表示当前时间。

以dp[i]来表示完成了i这个二进制数中所有为1的位置的对应的作业的状态

至于递推条件:(1)对当前状态i进行枚举他所为0的部分,j,若(i&j==0)则说明j这样作业尚未被完成。(2)然后将其添加,变成s=i|j,在更新dp[s]。在此我们要设一个标记数组,因为我们并不知道之前s是否已经被更新。

状压DP:

#include<iostream>
#include<cstdio>
#include<cstring>

const int N=1<<16;

struct node{
    char name[110];
    int dt,cost;
}work[30];

struct DP{
    int pre,time;
    int min_cost;
}dp[N];

int n,ans[30],vis[N];

int main(){

    //freopen("input.txt","r",stdin);

    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        memset(vis,0,sizeof(vis));
        for(int i=0;i<n;i++)
            scanf("%s%d%d",work[i].name,&work[i].dt,&work[i].cost);
        dp[0].pre=-1; dp[0].time=0; dp[0].min_cost=0;
        int M=1<<n;
        int i,j,k;
        for(i=0;i<M;i++)
            for(j=0;j<n;j++){
                k=1<<j;
                int tmp,cost;
                if((k&i)==0){
                    tmp=k|i;
                    cost=dp[i].time+work[j].cost-work[j].dt;
                    if(cost<0)
                        cost=0;
                    cost+=dp[i].min_cost;
                    if(vis[tmp]){
                        if(dp[tmp].min_cost>cost){
                            dp[tmp].min_cost=cost;
                            dp[tmp].pre=j;
                            dp[tmp].time=dp[i].time+work[j].cost;
                        }
                    }else{
                        vis[tmp]=1;
                        dp[tmp].min_cost=cost;
                        dp[tmp].pre=j;
                        dp[tmp].time=dp[i].time+work[j].cost;
                    }
                }
            }
        printf("%d\n",dp[M-1].min_cost);
        k=M-1;
        for(i=0;i<n;i++){
            ans[i]=dp[k].pre;
            k=k^(1<<dp[k].pre);
        }
        for(i=n-1;i>=0;i--)
            printf("%s\n",work[ans[i]].name);
    }
    return 0;
}
View Code

BFS: (还是状压快):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>

using namespace std;

const int N=1<<16;

struct node{
    char name[110];
    int dt,cost;
}work[30];

struct DP{
    int time,cost,status;   //时间 罚分 完成状态   
    char s[30]; //记录顺序 课程的编号   
}dp[N]; //下标表示状态 如dp[5] 化为 二进制 dp[101] 表示完成了编号 0 ,2的作业(从0开始) 

int n;

void BFS(){
    queue<DP> q;
    while(!q.empty())
        q.pop();
    DP cur,next;
    cur.time=0, cur.cost=0, cur.status=0, cur.s[0]='\0';
    dp[0]=cur;
    q.push(cur);
    while(!q.empty()){
        cur=q.front();
        q.pop();
        for(int i=0;i<n;i++){
            if((cur.status&(1<<i))==0){ //没有含有第i门课程 
                next.status=cur.status | (1<<i);
                next.time=cur.time+work[i].cost;
                next.cost=cur.cost;
                if(next.time>work[i].dt)
                    next.cost+=next.time-work[i].dt;
                if(dp[next.status].cost==-1 || next.cost<dp[next.status].cost){ //更新   
                    strcpy(next.s,cur.s);
                    int len=strlen(next.s);
                    next.s[len]=i+'0';
                    next.s[len+1]='\0'; //注意
                    dp[next.status]=next;
                    q.push(next);
                }
            }
        }
    }
}

int main(){

    //freopen("input.txt","r",stdin);

    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%s%d%d",work[i].name,&work[i].dt,&work[i].cost);
        memset(dp,0,sizeof(dp));
        for(int i=0;i<(1<<n);i++)
            dp[i].cost=-1;
        BFS();
        int M=(1<<n)-1;
        printf("%d\n",dp[M].cost);
        for(int i=0;i<n;i++)
            printf("%s\n",work[dp[M].s[i]-'0'].name);
    }
    return 0;
}
View Code

 

 

 

posted @ 2013-05-14 17:01  Jack Ge  阅读(282)  评论(0编辑  收藏  举报