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; }
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; }
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; }