HOJ 2798
1 #define MAXN 1000000
2 #define N 12
3 #define M 1<<(16)
4 int n,all,m;
5 int ans,sum;
6
7 int dp[M][N];
8 char mat[N][N];
9 map<string,int> mp;
10 map<string,int>::iterator p;
11 inline int fun(int x,int y) {
12 int xln = strlen(mat[x]);
13 int yln = strlen(mat[y]);
14 for(int i = 0;i<yln;i++)
15 for(int j = 0;j<xln;j++) {
16 if(mat[y][j] == mat[x][i]) {
17 return max(i,j) + max(xln - i,yln - j);
18 }
19 }
20 return xln + yln;
21 }
22
23 inline void fun() {
24 int i,j,temp;
25 p = mp.begin();
26 m = p->second;
27 dp[(1<<m)][m] = strlen(mat[m]);
28 for(i = 0;i<all;i++)
29 if(i & (1<<m))
30 for(j = 0;j<n;j++)
31 if((i&(1<<j)) && (i != (1<<j)) && j != m) {
32 temp = i - (1<<j);
33 for(int k = 0;k<n;k++)
34 if(temp & (1<<k))
35 dp[i][j] = min(dp[i][j],max(dp[temp][k] , fun(k,j)));
36 }
37
38 ans = MAXN;
39 for(i = 0;i<n;i++)
40 ans = min(ans,dp[all-1][i]);
41 printf("%d\n",ans*n - sum);
42 }
43
44 inline void init() {
45 sum=0;
46 for(int i = 0;i<n;i++)
47 scanf("%s",mat[i]),mp[mat[i]] = i,sum+=strlen(mat[i]);
48
49 //DP init
50 all = (1<<n);
51
52 for(int i = 0;i<all;i++)
53 for(int j = 0;j<n;j++)
54 dp[i][j] = MAXN;
55
56 }
57
58 int main() {
59 while(~scanf("%d",&n)) {
60 if(n == 0) break;
61 init();
62 fun();
63 }
64 }
65 // 选起始条件很重要啊,就是m
66 // 然后就是value函数
今天主要就是通过路行商问题,讲了讲状态压缩,留一道题的代码吧,最近对搜索比较有感觉,一直在总结,等总结好了贴出来一些自己的心得吧。
现在发现有很多种方法可以理解DP了,有半年前接触DP,一开始真是头大,看一个蒙一个,从认认真真弄过搜索后,DP可以清晰的理解成带贪心的记忆化搜索,每次搜索都保留在此状态的结果(隐式的搜索树),等下次在遇到这个状态的时候就进行比较(这就是通常说的DP重叠子结构),留下最优值,而以后的搜索建立在这个最优的状态上的,必然保持以后的最优(DP的独立子结构性质),这样保持了结果的最优。
DP的四个要素,状态,转移,初值,末值(就是结果)。
说多点就是确定要表示的状态,如何进行转移,设置初值,得到结果。说实话,我怎么看都像归纳法。。。
举个例子,比如旅行商问题,安装DP的要素去分析,我们要确定如何表示状态,这个是我一开始最心疼的地方,一开始看DP的时候,看每个题的解析都能看懂,当出现新题目的时候,完了,还是不会,下面我说说我是如何建状态的(待补充。。。。等看完更多的资料就来补充),通过搜索的思路(最近就是对搜索有感觉。。),搜索不好的童鞋要说了,我搜索不好怎么办,我真不知道了。。。如果用搜索做的话,从起点出发,遍历过所有的顶点,然后回到起点,假设是个完全图,那么肯定存在解,那我们搜索的步骤:
1 - 选择起点
2 - 用DFS扩展你选择的点,就是走过去
3 - 标记此点为走过
如果深度是图的大小n && 改点是起点 return sum(走过的路径的和)
否则转到 2;
返回最小的sum,结束。
看我们的DFS步骤,如果想用一个数组表示DFS搜索到的状态,应该有上面步骤3所有的条件,否则,否则就错了,因为少一个DFS就不能通过,不是吗,所以,DP[][]..[]应该要表示:当前这个点是哪个点(int型),你走过那些点(bool 型),那么状态就出来了DP[I][J][K][M][Q],I,J,K,M表示0-3这四个点是否走过,走过就是1,没走过就是0,Q就是当前在哪个点!(要是图的大小n不固定怎么办,不急,就是下面说的状态压缩,这里先说这个例子)
而这个数组的值就是问题要问的东西,最优解!
下面说 二 转移:
假如有四个顶点(下标从0-3),DP[1][0][1][1][3] 表示已经走过了第 0,2,3 ,个顶点,最后在第三个顶点上,此状态的时候,最少要走的路径的值,这个状态有那几个状态来的那,枚举一下就出来了,
DP[1][0][1][0][0] DP[1][0][1][0][2] DP[1][0][0][1][0] DP[1][0][0][1][3](没有次状态,为嘛?) DP[0][0][1][1][2] DP[0][0][1][1][3](也没有)
好了,状态枚举完了(枚举的就是DFS曾走过的路径),对应应该+的value;(图保的权值存在mat[][]数组里面)
mat[0][3] mat[2][3] mat[0][3] -- mat[2][3] --
这样取得最小的那个和就是我们要求的DP[1][0][1][1][3]这状态了。
这样就成功的转移了,我不写转移方程了,读者可以自己写下,就照着上面哪个过程,已经很详细了。
设置初值和取得末值也是很重要的一步,千万不要忽略!每个问题的初值和末值都不一样,根据你的状态来的,这个问题,初值显然了,想想搜索树从那开始的,就是步骤1,初值就是源点,DP[0][0][0][0][0]=0,其他要设置成MAX!
想想末值,就是搜索树进行完后的状态,也就是搜索步骤3中的return条件!当前面都走过的时候,并且当前点在源点时候跳出,这个状态不难写吧。
我的思路就讲完了,如何设计方程到设置值都映射到了搜索上去做。
其实DP很多的搜索树是类似的,就那状态压缩来说,如果用纯DP来做,可能内存会导致不足,比如上面说的问题,当有10+个顶点或者顶点不固定的时候,你的DP[][][][]...[]状态数组不是要开10+ +1维就是没法做。
明天看看资料在续写这个状态压缩,先赶公交了。。。
补充:关于状态压缩,自己感觉是有个同样的决策树,还是从TSP问题说起吧。
既然我们只需要记录这个点有没有走到,我们没必要用一整个维度表示吧,我们可以用一个维度来表示,必然说十进制整数6,换算成二进制就是 0110,我们可以用它来表示到达过 第一个顶点,第二个顶点。至于具体的处理网上多了是了,这里就不重点说这里了。
dp[i][j]的定义:状态为i时,以1号城市为起点,且我站在第j个地点对应的最短路径
下面拿HOJ 2665和HOJ 2798具体说下
//2665
dp[i][j]的定义:状态为i时,以1号机器为起点,且目前在第j个机器对应的最少消耗
//2798
dp[i][j]的定义:状态为i时,以m号球为起点,且目前在第j个球对应的最低高度
//2665
inline void fun() {
int i,j,temp;
for(i = 0;i<all;i++)
if(i & (1<<m))
for(j = 0;j<n;j++)
if((i&(1<<j)) && (i != (1<<j)) && j != m) {
temp = i - (1<<j);
for(int k = 0;k<n;k++)
if(temp & (1<<k))
dp[i][j] = min(dp[i][j],dp[temp][k] + a[k][j]);
}
ans = MAXN;
for(i = 0;i<n;i++)
ans = min(ans,dp[all-1][i]);
printf("%d\n",sum - ans);
}
//2798
inline void fun() {
int i,j,temp;
//for(int m = 0;m<n;m++) {
dp[(1<<m)][m] = mat[m] * 2.0;
for(i = 0;i<all;i++)
if(i & (1<<m))
for(j = 0;j<n;j++)
if((i&(1<<j)) && (i != (1<<j)) && j != m) {
temp = i - (1<<j);
for(int k = 0;k<n;k++)
if(temp & (1<<k))
dp[i][j] = min(dp[i][j],dp[temp][k] - mat[k] + mat[j] + fun(k,j));
}
//}
ans = MAXN;
for(i = 0;i<n;i++)
ans = min(ans,dp[all-1][i]);
printf("%d\n",ans - (int)ans >=0.5? (int)ans+1:(int)ans);
}
这两个代码神似啊!
这里的m都是起始点,从0000 0001 开始枚举,枚举有1点的状态,然后进行转移,因为进制是一位一位向上进的,则可证明以后的状态必然可以有:前面确定的状态(已经有结果的)来转移。
这就能成一个模板了,只需要改下转移时的最优函数就OK了。
HOJ 2798这个题时间内存都跑了第一,小小的得意一把,不得不说,这里有个小小的贪心优化,我不知到其他的童鞋写的,DP压缩的起始点也很重要,我倒是用题目给的 CASE 实验了一下:-),发现去最小值又快又对,果断+上了这个贪心,然后就洗具了~~。集训第一周有四天一个题也没A,纠结的几天改了代码风格,自己感觉更加的清晰和明了了,有利于跑思路,而且现在都在纸上写写伪代码,一次调试,然后提交。
昨天搜了搜python,觉得开始学习,今天群里面有个要买LPRS的,也没说价格,而且要买SDK,不知道有人卖给他吗,等我们的最后一步优化好了就果断卖掉,包括SDK,算法这玩意更新太快,自己写完都觉得已经有很多人会想到这个算法。
最近一直在linux上,装了个ubuntu新鲜新鲜,发现挺好使,唯一不足的是中国有些网页不兼容webkit内核的,而且中国的城墙封了好多地方,G+还得改hosts。。。

浙公网安备 33010602011771号