感觉最重要的还是状态的定义,定义对了就可以慢慢推方程式,然而某些题死都想不到正确的状态定义o(╯□╰)o。
Uva 12105:越大越好
题意:用不超过n(n<=100)根火柴摆一个尽量大的能被m(m<=3000)整除的正整数
题解:dp[i][j]表示i位数mod m等于j需要的最少火柴数,然后找到最大的i使dp[i][0]<=n,再从最高位开始dfs,先尝试9检查dp[i-1][((mod-i*A[pos]%m)%m+m)%m]的值判断剩下的火柴是否够用。
//c[i](i≤9)表示数字i需要的火柴数
void print(int pos,int mod,int now){
if(pos==1)
for(int i=9;i>=0;i--)
if(i%m==mod&&c[i]<=now){
putchar(i+48);return ;
}
for(int i=9;i>=0;i--){
int tmp=((mod-i*A[pos]%m)%m+m)%m;//A[i]表示10^(i-1)
if(dp[pos-1][tmp]<=now-c[i]){
putchar(i+48);
print(pos-1,tmp,now-c[i]);
break ;
}
}
}
int maxlen=n>>1;//全部拼1
memset(dp,0x3f,sizeof(dp));
for(int j=0;j<=min(m-1,9);j++)
dp[1][j]=c[j];
if(m<=9) dp[1][0]=min(dp[1][0],c[m]);
for(int i=1;i<=maxlen;i++)
for(int j=0;j<min(pow(10,i),m*1.0);j++)
for(int k=0;k<=9;k++)
if(dp[i+1][(j*10+k)%m]>dp[i][j]+c[k])
dp[i+1][(j*10+k)%m]=dp[i][j]+c[k];
Uva 12099:书架
题意:3个架子,一堆书(给出高度和宽度),放入后,要求每层宽度最大值乘以总高度最小。(每层必须要放书)每层高度为该层最高的书,宽度为该层书的总宽度。
题解:dp[i][a][b]表示考虑到第i本书,第二层宽度为a,第三层宽度为b时二层和三层的最小高度(最高的那本放第一层,所以不用考虑第一层的高度)。dp前把书按高度从大到小排序,dp时就不需要再考虑高度了(先放的一定最大)。(内存可能开不下,滚动数组)
bool now=0; for(int i=1;i<=n;i++){ for(int j=0;j<=sum[i];j++) for(int k=0;k<=sum[i]-j;k++) dp[now^1][j][k]=inf; for(int j=0;j<=sum[i-1];j++) for(int k=0;k<=sum[i-1]-j;k++){ dp[now^1][j][k]=min(dp[now^1][j][k],dp[now][j][k]); dp[now^1][j+book[i].w][k]=min(dp[now^1][j+book[i].w][k],dp[now][j][k]+f(j,book[i].h)); dp[now^1][j][k+book[i].w]=min(dp[now^1][j][k+book[i].w],dp[now][j][k]+f(k,book[i].h)); } now^=1; } int ans=inf; for(int i=1;i<=sum[n];i++) for(int j=1;j<=sum[n]-i;j++) if(dp[now][i][j]<inf){ int w=max(max(i,j),sum[n]-i-j); int h=dp[now][i][j]+book[1].h; ans=min(ans,w*h); }
Uva 1204:Fun Game
题意:一群小孩(至少两个)围成一圈做游戏。每一轮从某个小孩开始往他左边或者右边传手帕。一个小孩拿到手帕后在手帕上写上自己的性别,男孩写B,女孩写G,然后按照相同的方向传递给下一个小孩,每一轮都可能在任何一个小孩写完后停止,现在游戏已经进行了n轮,已知n轮中每轮手帕上留下的字,问最少可能有几个小孩。
题解:等我过了再写
除了状态的定义之外还有那么一些题目,状态的定义和转移的思路比较好想,但实现起来恶心到吐O__O”…
Uva 1412:基金管理
题意:现在手里有C美元的现金,但没有股票,给你m(1≤m≤100)天的时间和n(1≤n≤8)支股票供你购买,手中最多持有k支股票,要求最后一天不持有任何股票且剩余的钱最多(股票只能用现金买)每支股票有三个参数ci(一支股票的价格),si(一手股票的支数),ki(最多持有的手数)。每天要么不动,要么买一手或买一手股票。输出最后一天最多剩余的钱和操作流程。
题解:因为n≤8,所以用vector就可以表示所有情况,再用map赋予每个集合对应的标号,即相当于状态压缩了。方程式比较显然。本来写的是记忆化搜索但越写越恶心,最后还是用了紫书和网上题解的写法。
void dfs(int stock,vector<int>& lots,int totlot) {//totlot表示目前所有股票手数 if(stock==n) id[lots]=states.size(),states.push_back(lots); else for(int i=0;i<=k[stock]&&totlot+i<=kk;i++) lots[stock]=i,dfs(stock+1,lots,totlot+i); } void init() { vector<int> lots(n);//保存每个股票买了几种 states.clear(); ID.clear(); dfs(0,lots,0);//初始化映射表,每一种购股数情况映射到整数下标 for(int s=0;s<states.size();s++) {//遍历所有可能的购股情况 int totlot=0; for(int i=0;i<n;i++) totlot+=states[s][i];//第一种情况的所有股票数 for(int i=0;i<n;i++){ buy_next[s][i]=sell_next[s][i]=-1;//标记没有空的情况 if(states[s][i]<k[i]&&totlot<kk){//小于说明还能买一支 vector<int> newstate=states[s]; newstate[i]++; buy_next[s][i]=id[newstate]; } if(states[s][i]>0){//大于0说明还有,还能卖 vector<int> newstate=states[s]; newstate[i]--; sell_next[s][i]=id[newstate];//标明转换到的状态 } } } } void update(int day,int s,int s2,double v,int o) { if(v>dp[day+1][s2]){ dp[day+1][s2]=v; opt[day+1][s2]=o; pre[day+1][s2]=s; } } double DP(){ for(int day=0;day<=m;day++) for(int s=0;s<states.size();s++) dp[day][s]=-1.0; dp[0][0]=c; for(int day=0;day<m;day++) for(int s=0;s<states.size();s++) { double v=dp[day][s]; if(v<0) continue; update(day,s,s,v,0); for(int i=0;i<n;i++){ if(buy_next[s][i]>=0&&v>=price[i][day]-1e-3) update(day,s,buy_next[s][i],v-price[i][day],i+1); if(sell_next[s][i]>=0) update(day,s,sell_next[s][i],v+price[i][day],-i-1); } } return dp[m][0]; } void print_ans(int day,int s) {// if(day==0) return; print_ans(day-1,pre[day][s]); if(opt[day][s]==0) printf("HOLD\n"); else if(opt[day][s]>0) printf("BUY %s\n", name[opt[day][s]-1]); else printf("SELL %s\n",name[-opt[day][s]-1]); }
Uva 10618:跳舞机
题意:
题解:
这道题紫书上说得很清楚,状态和转移都很好理解,但限制和细节很多
注意四个非法情况:
if(ta==tb) return -1; else if(ta==pos['R']&&tb==pos['L']) return -1; else if(a==pos['R']&&tb!=b) return -1; else if(b==pos['L']&&ta!=a) return -1;
Uva 1626:括号序列
题意:给定两种括号 然后要求输出最短的规范字符串, 就是每个括号都能找得到对应的括号 并且符合递归定义
题解:首先想到的肯定是搜索(去除外层括号+中间拆分),但如果不记忆化重复计算很多,记忆化的话string和map都很慢,所以搜索会TLE。然后dp的思路和搜索是一样的。
P.s:此题最大的坑在于输入空行的判断和输出与下一组输入之间有空行。
int n=strlen(str); for(int i=0;i<n;i++)dp[i][i]=1;
for(int j=1;j<n;j++) for(int i=0;i+j<n;i++){ dp[i][i+j]=inf; if(check(i,i+j)){ dp[i][i+j]=dp[i+1][i+j-1]; pre[i][i+j]=make_pair(i+1,i+j-1); } for(int k=0;k<=j;k++) if(dp[i][i+j]>dp[i][i+k]+dp[i+k+1][i+j]){ dp[i][i+j]=dp[i][i+k]+dp[i+k+1][i+j]; pre[i][i+j]=make_pair(i,i+k); } }
在dp的时候还会出现一些单独算一部分的cost不方便时可以考虑计算当前操作对于总体的影响。
Uva 1625:颜色的长度
题意:给出两个长度分别为n和m的颜色序列(n,m<=5000)都是由大写字母组成,要求按照顺序合并成同一个序列,即每次可以把一个序列开头的颜色放在新序列的尾部对于每一种颜色c,跨度L(c)是最大位置和最小位置之差,问题是:找一种合并的方式使得L(c)的总和最小。
题解:首先,每种颜色单独去统计每次出现这种颜色时他的最开始的位置是很麻烦的。但所有颜色一起统计在第一个串i的位置和在第二个串j的位置未结束但是已经开始的颜色的总数就比较方便,因为最后统计的总的sigema(L)的时候这些位置都是要算进去的则:状态d(i,j)表示第一个串剩下i和第二个串剩下j时所得到的sigma(L)的最小值则状态转移为:d(i,j) = min(d(i+1,j)d(i,j+1))+res(i,j);d(i+1,j)表示从第一个串拿走一个d(i,j+1)表示从第二个串拿走一个res(i,j)表示当前第一个串i,第二个串j位置时未结束但是已经开始的颜色的总数。
for(int i=0;i<=n;i++) for(int j=0;j<=m;j++){ if(i==0&&j==0) continue ; dp[i][j]=inf; if(i) dp[i][j]=dp[i-1][j]+g[i][j]; if(j) dp[i][j]=min(dp[i][j],dp[i][j-1]+g[i][j]); }
Uva 1336:修缮长城
题意:长城(视作x正半轴)有n处破损。有一个智能修复机器人,它的初始位置和移动速度已知。每处破损处都有一组参数(x,c,d),x表示位置,c、d表示在时间t后再修复该处破损的花费为d*t+c。求用一个机器人修复所有破损的最小花费。
题解:要想最终代价最低,就不能跳跃着修复,也就是经过一段时间后已经修复好的破损应是一段连续区间。定义dp(i,j,k)表示修好(i,j)后机器人停留在k(0表示在左端,1表示在右端)端的费用。修复某处破损的代价虽然不是定值,但却是随着时间线性增长的,所以当修复完一处或一段破损时,修复其他破损的费用可以算出来,只需将其累加到当前状态即可,也可以视作修复某处破损产生的时间代价。注意需要用double。
左边:
double a=dfs(l+1,r,flag),ta=(arr[l+1].x-arr[l].x)/v; double b=dfs(l+1,r,flag^1),tb=(arr[r].x-arr[l].x)/v; double d=sum[n]-(sum[r]-sum[l]); return dp[l][r][flag]=min(a+d*ta,b+d*tb)+arr[l].c;
右边:
double a=dfs(l,r-1,flag),ta=(arr[r].x-arr[r-1].x)/v; double b=dfs(l,r-1,flag^1),tb=(arr[r].x-arr[l].x)/v; double d=sum[n]-(sum[r-1]-sum[l-1]); return dp[l][r][flag]=min(a+d*ta,b+d*tb)+arr[r].c;