dp训练

1.

假设你想以最美观的方式布置花店的橱窗。现在你有F束不同品种的花束,同时你也有至少同样数量的花瓶被按顺序摆成一行。这些花瓶的位置固定于架子上,并从1至V顺序编号,V是花瓶的数目,从左至右排列,则最左边的是花瓶1,最右边的是花瓶V。花束可以移动,并且每束花用1至F间的整数唯一标识。标识花束的整数决定了花束在花瓶中的顺序,如果I<J,则令花束I必须放在花束J左边的花瓶中。
  例如,假设一束杜鹃花的标识数为1,一束秋海棠的标识数为2,一束康乃馨的标识数为3,所有的花束在放入花瓶时必须保持其标识数的顺序,即:杜鹃花必须放在秋海棠左边的花瓶中,秋海棠必须放在康乃馨左边的花瓶中。如果花瓶的数目大于花束的数目。则多余的花瓶必须空置,且每个花瓶中只能放一束花。
  每一个花瓶都具有各自的特点。因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以美学值(一个整数)来表示,空置花瓶的美学值为零。
在上述例子中,花瓶与花束的不同搭配所具有的美学值,如下表所示。

                              花  瓶
              1      2      3      4      5
     1 (杜鹃花)       7      23     -5       -24      16
     2 (秋海棠)       5      21     -4       10       23
     3 (康乃馨)      -21       5      -4       -20      20

     例如,根据上表,杜鹃花放在花瓶2中,会显得非常好看;但若放在花瓶4中则显得十分难看。
     为取得最佳美学效果,你必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。如果有不止一种的摆放方式具有最大的美学值,你只要输出字典序最小的那种摆放方式。

训练第一题:

重要在于输出方案,dp的时候顺便记录一下就好;

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 using namespace std;
 6 const int maxn(105);
 7 #define LL long long
 8 #define up(i,j,n) for(int i=(j);(i)<=(n);i++)
 9 int n,m,f[maxn][maxn],pre[maxn][maxn],id[maxn][maxn],a[maxn][maxn],w[maxn];//a[i][j]表示第i花在j花瓶的价值
10 int main(){
11     freopen("dealing.in","r",stdin);
12     freopen("dealing.out","w",stdout);
13     memset(f,-10,sizeof(f));
14     memset(pre,-10,sizeof(pre));
15     scanf("%d%d",&n,&m);
16     up(i,1,n)up(j,1,m)scanf("%d",&a[i][j]);
17     up(i,0,m)f[0][i]=0;
18     up(i,1,n)up(j,i,m)up(k,i-1,j-1){
19         f[i][j]=max(f[i][j],f[i-1][k]+a[i][j]);
20         if(pre[i][j]<f[i][j])pre[i][j]=f[i][j],id[i][j]=k;
21     }
22     int ans=-10000000;
23     int t=m;
24     up(i,n,m)if(ans<f[n][i])ans=f[n][i],t=i;
25     printf("%d
",ans);
26     w[n]=t;
27     for(int i=n-1;i>=1;i--)
28         w[i]=id[i+1][w[i+1]];
29     for(int i=1;i<=n;i++)printf("%d ",w[i]);
30     return 0; 
31 }
View Code

2.

在《Harry Potter and the Deathly Hallows》中,Harry Potter他们一起逃亡,现在有许多的东西要放到赫敏的包里面,但是包的大小有限,所以我们只能够在里面放入非常重要的物品,现在给出该种物品的数量、体积、价值的数值,希望你能够算出怎样能使背包的价值最大的组合方式,并且输出这个数值,赫敏会非常地感谢你。

写得比较蠢,原因是自己把ij同时用了......

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 using namespace std;
 6 const int maxn(5005);
 7 #define LL long long
 8 #define up(i,j,n) for(int i=(j);(i)<=(n);(i)++)
 9 int w[maxn],v[maxn],cnt[maxn],n,m;
10 int f[maxn];
11 int main(){
12     freopen("dealing.in","r",stdin);
13     freopen("dealing.out","w",stdout);
14     scanf("%d%d",&n,&m);
15     up(i,1,n)scanf("%d%d%d",&cnt[i],&w[i],&v[i]);
16     for(int i=1;i<=n;i++){
17         if(cnt[i]*w[i]>=m)
18             for(int j=w[i];j<=m;j++)f[j]=max(f[j],f[j-w[i]]+v[i]);
19         else {
20             int k=1;
21             while(cnt[i]){
22                 if(k>cnt[i])k=cnt[i],cnt[i]=0;
23                 else cnt[i]-=k;
24                 int W=w[i]*k,V=v[i]*k;
25                 for(int j=m;j>=W;j--)f[j]=max(f[j],f[j-W]+V);
26                 k<<=1;
27             }
28         }
29     }
30     printf("%d
",f[m]);
31     return 0;
32 }
View Code

3.

在《Harry Potter and the Chamber of Secrets》中,Ron的魔杖因为坐他老爸的Flying Car撞到了打人柳,不幸被打断了,从此之后,他的魔杖的魔力就大大减少,甚至没办法执行他施的魔咒,这为Ron带来了不少的烦恼。这天上魔药课,Snape要他们每人配置一种魔药(不一定是一样的),Ron因为魔杖的问题,不能完成这个任务,他请Harry在魔药课上(自然是躲过了Snape的检查)帮他配置。现在Harry面前有两个坩埚,有许多种药材要放进坩埚里,但坩埚的能力有限,无法同时配置所有的药材。一个坩埚相同时间内只能加工一种药材,但是不一定每一种药材都要加进坩埚里。加工每种药材都有必须在一个起始时间和结束时间内完成(起始时间所在的那一刻和结束时间所在的那一刻也算在完成时间内),每种药材都有一个加工后的药效。现在要求的就是Harry可以得到最大的药效。

这道题以前没真正理解,重写一遍,又有了新体会;

这题首先对物品按结束时间排序,然后进行dp;

为什么这么做;

这题的难点之一在于无法对状态进行有效的转移;但是这么做使物品有序后,就能发现状态可以转移了;

有时候排序对于优化dp是一种不错的方法;

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<algorithm>
 6 using namespace std;
 7 const int maxn(505);
 8 #define LL long long
 9 #define up(i,j,n) for(int i=(j);(i)<=(n);(i)++)
10 int f[maxn][maxn];
11 int n,t;
12 struct node{
13     int x,y,v;
14     bool operator<(const node& b)const{return y<b.y;}
15     int operator-(int b)const{return x-b;}//这个是无聊写得......
16 }a[maxn];
17 int main(){
18     freopen("dealing.in","r",stdin);
19     freopen("dealing.out","w",stdout);
20     scanf("%d%d",&t,&n);
21     up(i,1,n)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].v);
22     sort(a+1,a+n+1);
23     for(int i=1;i<=n;i++){
24         for(int j=t;j>=1;j--)
25             for(int k=t;k>=0;k--){
26                 if(j>=a[i].y)f[j][k]=max(f[j][k],f[a[i]-1][k]+a[i].v);
27                 if(k>=a[i].y)f[j][k]=max(f[j][k],f[j][a[i]-1]+a[i].v);
28             }
29     }
30     printf("%d
",f[t][t]);
31     return 0;
32 }
View Code

4.

有两行自然数,UP[1..N],DOWN[1..M],如果UP[I]=DOWN[J]=K,那么上行的第I个位置的数就可以跟下行的第J个位置的数连一条线,称为一条K匹配,但是同一个位置的数最多只能连一条线。另外,每个K匹配都必须且至多跟一个L匹配相交且K≠L  。现在要求一个最大的匹配数。
例如:以下两行数的最大匹配数为8
::点击图片在新窗口中打开::

这题以前同样没怎么理解,于是再写一遍......

设f[i][j]表示前i个a元素和前j个b元素一共能构成多少匹配;

四重循环枚举,数据比较水,直接跑过了;

5.

帅帅经常更同学玩一个矩阵取数游戏:对于一个给定的n*m的矩阵,矩阵中的每个元素aij据为非负整数。游戏规则如下:
  1. 每次取数时须从每行各取走一个元素,共n个。m次后取完矩阵所有的元素;
  2. 每次取走的各个元素只能是该元素所在行的行首或行尾;
  3. 每次取数都有一个得分值,为每行取数的得分之和;每行取数的得分 = 被取走的元素值*2^i,其中i表示第i次取数(从1开始编号);
      4. 游戏结束总得分为m次取数得分之和。

  帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。

原本想象成区间合并+特判,后来发现不对,每次只能在区间的两边增加数,这样才能保证合法;

正序逆序都可以做,建议逆序写吧,好写;

高精度,code比以前快了不少;

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstdlib>
  4 #include<cstring>
  5 #include<algorithm>
  6 using namespace std;
  7 const int maxn(105);
  8 #define LL long long
  9 #define up(i,j,n) for(int i=(j);(i)<=(n);(i)++)
 10 int T,n;
 11 char s[maxn];
 12 
 13 struct bignum{
 14     
 15     int len,num[105];
 16     void print(){for(int i=len;i>=1;i--)cout<<num[i];cout<<endl;}
 17     void mem(){memset(num,0,sizeof(num));len=1;}
 18     bignum(){memset(num,0,sizeof(num));len=1;}
 19     bignum operator=(char* s){
 20         int n=strlen(s+1);
 21         memset(num,0,sizeof(num));
 22         for(int i=1;i<=n;i++)num[i]=s[n-i+1]-'0';
 23         len=n;
 24         return *this;
 25     }
 26     bignum operator=(int x){
 27         char s[maxn];
 28         sprintf(s+1,"%d",x);
 29         return *this=s;
 30     }
 31     friend bignum operator+(bignum a,bignum b){
 32         bignum c;
 33         int len=a.len;
 34         if(len<b.len)len=b.len;
 35         for(int i=1;i<=len;i++){
 36             c.num[i]+=a.num[i]+b.num[i];
 37             if(c.num[i]>=10)c.num[i]-=10,c.num[i+1]++;
 38         }
 39         while(c.num[len+1])len++;
 40         while(!c.num[len]&&len>1)len--;
 41         c.len=len;
 42         return c;
 43     }
 44     friend bignum operator*(bignum a,int x){
 45         bignum c;
 46         c.len=a.len;
 47         int len=a.len;
 48         for(int i=1;i<=len;i++){
 49             c.num[i]+=a.num[i]*x;
 50             int temp=i;
 51             while(c.num[temp]>=10)c.num[temp+1]+=c.num[temp]/10,c.num[temp]%=10,temp++;
 52             if(temp>len)len=temp;
 53         }
 54         while(!c.num[len]&&len>1)len--;
 55         while(c.num[len+1])len++;
 56         c.len=len;
 57         return c;
 58     }
 59     friend bignum operator*(bignum a,bignum b){
 60         bignum c;
 61         int len=a.len;
 62         if(len<b.len)len=b.len;
 63         for(int i=1;i<=a.len;i++)
 64             for(int j=1;j<=b.len;j++){
 65                 int k=i+j-1;
 66                 c.num[k]+=a.num[i]*b.num[j];
 67                 while(c.num[k]>=10)c.num[k+1]+=c.num[k]/10,c.num[k]%=10,k++;
 68                 len=k;
 69             }
 70         while(c.num[len]>=10)c.num[len+1]+=c.num[len]/10,c.num[len]%=10,len++;
 71         while(c.num[len+1])len++;
 72         while(!c.num[len]&&len>1)len--;
 73         c.len=len;
 74         return c;
 75     }
 76     bool operator<(const bignum &b)const{
 77         if(len<b.len)return 1;
 78         for(int i=len;i>=1;i--)if(num[i]!=b.num[i])return num[i]<b.num[i];
 79         return 0;
 80     }
 81 }a[maxn],ans,f[maxn][maxn],mi[maxn];
 82 int main(){
 83     freopen("dealing.in","r",stdin);
 84     freopen("dealing.out","w",stdout);
 85     scanf("%d%d",&T,&n);
 86     mi[0]=1;
 87     for(int i=1;i<=n;i++)mi[i]=mi[i-1]*2;
 88     while(T--){
 89         up(i,1,n){scanf("%s",s+1);a[i]=s;}
 90         up(i,1,n)f[i][i]=a[i];
 91         for(int p=2;p<=n;p++)
 92             for(int i=1;i+p-1<=n;i++){
 93                 int j=i+p-1;
 94                 f[i][j].mem();
 95                 f[i][j]=max(f[i+1][j]*2+a[i],f[i][j-1]*2+a[j]);
 96             }
 97         f[1][n]=f[1][n]*2;
 98         //f[1][n].print();
 99         ans=ans+f[1][n];
100     }
101     ans.print();
102     return 0;
103 }
View Code

 5.

小x在学习数列。他想到一个数学问题:
现在有N个数的数列。现在你定义一个子序列是数列的连续一部分,子序列的值是这个子序列中最大值和最小值之差。
给你这N个数,小x想知道所有子序列的值得累加和是多少。

这题算是练习一下思维;

首先要想到答案是所有最大值的和与所有最小值的和的差;

用单调栈维护一下区间最小值和区间最大值,顺便处理一下它们的和;

 1 /*
 2 chad
 3 p1859
 4 2016/11/3
 5 */
 6 #include<iostream>
 7 #include<cstdio>
 8 #include<cstdlib>
 9 #include<cstring>
10 #include<algorithm>
11 using namespace std;
12 const int maxn(303000);
13 #define LL long long
14 #define up(i,j,n) for(int i=(j);(i)<=(n);(i)++)
15 LL a[maxn],n;
16 LL Min[maxn],Max[maxn],Min_=0,Max_=0;
17 LL Suma,Sumb;
18 LL ans=0;
19 namespace IO{
20     char buf[1<<15],*fs,*ft;
21     int gc(){return (fs==ft&&((ft=(fs=buf)+fread(buf,1,1<<15,stdin)),fs==ft))?0:*fs++;}
22     int read(){
23         int x=0,ch=gc(),f=0;
24         while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=gc();}
25         while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();}
26         return f?-x:x;
27     }
28 }using namespace IO;
29 int main(){
30     freopen("dealing.in","r",stdin);
31     freopen("dealing.out","w",stdout);
32     n=read();
33     up(i,1,n)a[i]=read();
34     up(i,1,n){
35         while(Min_&&a[Min[Min_]]>a[i])Suma-=a[Min[Min_]]*(Min[Min_]-Min[Min_-1]),Min_--;
36         Suma+=a[i]*(i-Min[Min_]);Min[++Min_]=i;
37         while(Max_&&a[Max[Max_]]<a[i])Sumb-=a[Max[Max_]]*(Max[Max_]-Max[Max_-1]),Max_--;
38         Sumb+=a[i]*(i-Max[Max_]);Max[++Max_]=i;
39         ans+=Sumb-Suma;
40     }
41     cout<<ans<<endl;
42     return 0;
43 }
View Code

6.

Goneril是一只非常缺觉的奶牛。她的一天被平均分割成N段(3 <= N <= 3,830),但是她要用其中的B段时间(2 <= B < N)睡觉。每段时间都有一个效用值U_i(0 <= U_i <= 200,000),只有当她睡觉的时候,才会发挥效用。
有了闹钟的帮助,Goneril可以选择任意的时间入睡,当然,她只能在时间划分的边界处入睡、醒来。
Goneril想使所有睡觉效用的总和最大。不幸的是,每一段睡眠的第一个时间阶段都是“入睡”阶段,而且不记入效用值。
时间阶段是不断循环的圆(一天一天是循环的嘛),假如Goneril在时间N和时间1睡觉,那么她将得到时间1的效用值。

环形dp是我深恶痛绝的类型之一;

先照旧敲暴力,设f[i][j]表示前i个中选j个的最大得分,然后写了一个n^3的转移,最后发现自己貌似无法处理环形的问题,而且n^3也爆掉了;

尽管过掉了大部分数据,但然并卵;

想了一会儿,没法写了;

题解挺神,设f[i][j][1/0]表示第i个节点选或不选,前面一共选j个的最大得分;

这玩意是个n^2的转移方程;

为什么可以做到n^2,因为在i点只有两种选择,取或不取,取的话也有两种选择,不取一种选择;我设置的状态不够简洁;

这个状态也非常有利于我们下面的对环的处理;

如果没有从n转到1的线段的话,普通的dp就可以解决;

如果有从n转到1的线段,可以发现点1是一定要取的,必取无疑,就可以将f[i][1][1](i∈[2,n])全部设为-inf,这样在转移的时候,点1一定会成为第1个被选的的,这样就可以转移了;

 1 /*
 2 chad
 3 p1875
 4 2016/11/3
 5 */
 6 #include<iostream>
 7 #include<cstdio>
 8 #include<cstdlib>
 9 #include<cstring>
10 #include<algorithm>
11 using namespace std;
12 const int maxn(3840),inf(100000000);
13 #define LL long long
14 #define up(i,j,n) for(int i=(j);(i)<=(n);(i)++)
15 namespace IO{
16     char buf[1<<15],*fs,*ft;
17     int gc(){return (fs==ft&&((ft=(fs=buf)+fread(buf,1,1<<15,stdin)),fs==ft))?0:*fs++;}
18     int read(){
19         int x=0,ch=gc(),f=0;
20         while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=gc();}
21         while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();}
22         return f?-x:x;
23     }
24 }using namespace IO;
25 int n,b;
26 int a[maxn];
27 int f[maxn][maxn][2];
28 int main(){
29     //freopen("dealing.in","r",stdin);
30     //freopen("dealing.out","w",stdout);
31     scanf("%d%d",&n,&b);
32     up(i,1,n)a[i]=read();
33     for(int i=2;i<=n;i++)
34         for(int j=2;j<=b;j++){
35             f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]);
36             f[i][j][1]=max(f[i-1][j-1][1]+a[i],f[i-1][j-1][0]);
37         }
38     int ans=max(f[n][b][1],f[n][b][0]);
39     memset(f,0,sizeof(f));
40     for(int i=2;i<=n;i++)f[i][1][1]=-inf;
41     for(int i=2;i<=n;i++)
42         for(int j=2;j<=b;j++){
43             f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]);
44             f[i][j][1]=max(f[i-1][j-1][1]+a[i],f[i-1][j-1][0]);
45         }
46     ans=max(ans,f[n][b][1]+a[1]);
47     printf("%d
",ans);
48     return 0;
49 }
View Code

7.

某天,小x在玩一个经典小游戏——zumo。
zumo游戏的规则是,给你一段长度为n的连续的彩色珠子,珠子的颜色不一定完全相同,但是,如果连续相同颜色的珠子大于等于k个,这些珠子就会消失。当然,最初的状态可能不必要直接消掉一些珠子(见样例)。现在你有无穷个所有颜色的珠子,并且你可以在任意地方插入珠子。
现在提出问题:给你一个固定的状态,你最少需要用多少个小球,才能将所有的小球消去。

分析了下题意,发现数列当前这样明显不好做,可以看出连续的颜色相同的合并比较好做,先合并一下;

合并完,设f[i][j]表示把i到j部分的珠子消掉最少花费;

然后想想怎么转移,然后敲了一会发现总是不过,就知道思路出问题了;

看题解:这题解very  good!

我想了想,f[i][j]这个状态的缺点在于转移的时候不好转移,因为如果要区间合并,我就需要知道区间的另外的一些信息,但我这个状态中找不到这个信息,于是我抓瞎了......

这个故事告诉我们,一旦dp是发现状态没法转移了,信息不足,再多设一维状态,或者看看是否有多余状态,可以优化;

dp这东西,一旦思路好了,走起来就很顺,所以发现此路不通就要及时调整思路;

/*
chad
p1875
2016/11/3
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn(105),inf(100000000);
#define LL long long
#define up(i,j,n) for(int i=(j);(i)<=(n);(i)++)
namespace IO{
    char buf[1<<15],*fs,*ft;
    int gc(){return (fs==ft&&((ft=(fs=buf)+fread(buf,1,1<<15,stdin)),fs==ft))?0:*fs++;}
    int read(){
        int x=0,ch=gc(),f=0;
        while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=gc();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();}
        return f?-x:x;
    }
}using namespace IO;
int n,K;
int a[maxn],b[maxn],top=0,v[maxn][maxn][2];
int f[maxn][maxn][maxn];
int w(int x){return K-x>0?K-x:0;}
int dfs(int x,int y,int k){
    if(f[x][y][k]!=-1)return f[x][y][k];
    if(x==y)return f[x][y][k]=w(b[x]+k);
    f[x][y][k]=inf;
    f[x][y][k]=min(f[x][y][k],dfs(x,y-1,0)+w(b[y]+k));
    for(int i=x;i<y;i++)
        if(a[i]==a[y])f[x][y][k]=min(f[x][y][k],dfs(x,i,b[y]+k)+dfs(i+1,y-1,0));
    return f[x][y][k];
}
int main(){
    freopen("dealing.in","r",stdin);
    freopen("dealing.out","w",stdout);
    n=read(),K=read();
    up(i,1,n){
        int x=read();
        if(x==a[top])b[top]++;
        else a[++top]=x,b[top]=1;
    }
    n=top;
    memset(f,-1,sizeof(f));
    printf("%d
",dfs(1,n,0));
    return 0;
}
View Code

8.

小x,小y和小z在焦作一中东南角的臭水沟里发现了一堆宝藏。
这堆宝藏是由n (1 <= n <= 20)包元宝组成,每包含si (1 <= si <= 100)个元宝,三个人很光明正大的决定把这些元宝私分。
当然,他们都希望把这批宝藏尽可能的平分,他们又约定不能把包给拆开。这样,平分的标准就是获得元宝个数最多的那个人的元宝个数尽可能的小,他们约定,谁先算出,谁可以先挑。
例如:现在有8包元宝,每包分别有元宝:2 4 5 8 9 14 15 20
最公平的分法是:
1: 2 9 15  一共获得 26
2: 4 8 14  一共获得26
3:  5 20    一共获得 25
那么26就是我们需要的
现在,眼红的你,能算出获得元宝最多的那个人最少能获得多少元宝么?

见题目先敲贪心+dfs,水过;

再想想dp怎么搞;

设f[i][j][k]表示到第i个物品,一个人j元宝,一个人k元宝,另一个人可以算出来,这样可以暴力转移了......

突然发现自己写的贪心+dfs有点小麻烦了;

9.

 小x得到了一个珍贵的项链,这个项链由一圈N个透明的珠子组成。
某天晚上,小x做了一个梦,这个透明的项链被染上了各种颜色,在醒来后,小x还清楚的记得项链的各个珠子颜色。而将项链染成梦中的颜色是小x最大的梦想。
某位大神出现了,给了小x一个神奇的板刷,这个板刷可以将不超过K个连续的珠子刷成一个颜色,新刷的颜色会覆盖以前的颜色。而获得这个板刷的要求就是,大神要小x回答出最少用多少次板刷,可以完成小x的任务。
比如小x的项链有5颗珠子,要染成的颜色分别是:2 1 2 1 2.刷子每次最多能刷3个珠子,可以先把编号为2 3 4的珠子 染成颜色1,编号为5 1的珠子染成颜色2,编号为3的珠子染成颜色2.最少用3次刷子就可以得到小x想要的颜色。
现在小x把任务交给了要参加noi2013 的你,如果你能帮助他,你将获得通过noi2013所足够的rp。

这题很不错(题面被老师修改,原题是sdut1309);

我的思路是设个三维的状态f[i][j][w]表示当前ij区间所有的颜色是w,将这个区间的所有颜色刷成规定颜色的最小花费;

状态转移方程:

f[i][j]=f[i][j-1]  a[i]==a[j]&&j-i+1<=k   1    

f[i][j]=min(f[i][p]+f[p+1][j])  a[i]==a[p]&&p-i+1<=k  //这个比较容易想到

这题我最初想的时候,想的是在以最外面珠子的颜色的涂了一层后,需要记录一下当前珠子的颜色,但这么搞很明显不太科学,思路复杂;

但不记录又很明显有后效性,因为在先涂了一种颜色后,里面的有些珠子已经满足了条件,不用再涂了,如果再涂一遍会挂;

这时候的1式精妙之处就体现出来了,它并没有完全抛弃最外层,只是去掉了最外面的一个珠子,这样就很巧妙的避免了记录颜色这个状态,因为我们这样其实是默认了,最左端珠子的颜色是当前区间的颜色,然后在一步一步的递归中最后将刷的个数加上去,比三维的状态好的太多了;

dp暂时告一段落;

原文地址:https://www.cnblogs.com/chadinblog/p/6026571.html