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 }
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 }
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 }
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 }
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 }
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 }
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; }
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暂时告一段落;