状态压缩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

 

 

 

你可能感兴趣的:(学习)