· 笔记 ·
动态规划求解的问题,一般有两个特征:
①最优子结构
②重叠子问题
所以算法的设计思路不在于一下子就想到了某个问题可以使用DP算法,
而在于先看能不能用穷举法,如果可以用问题可以分解,分治法+穷举可以解决;
如果问题包含重叠字问题,并且是求解最优解,那么此时用动态规划。
· tips ·
【1】精度问题
【2】重叠子问题
【3】初始化
【4】后效性
· 要点归纳(个人) ·
【1】卡常问题:
(1)sqrt能只开一次就只开一次,
我次次都开,tle 6个点
(2)精度问题
不要先开sqrt再加,可能丢失精度,虽然这里问题不大
(3)不四舍五入 的 输出问题
最后代码:
#include<cstdio> #include<cstdlib> #include<cmath> #include<algorithm> #include<cstring> using namespace std; int n,m; const int N=1003; int sum[N],st[N][N],f[403][N]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&sum[i]),sum[i]= sum[i]*sum[i] + sum[i-1]; //memset(f,0x3f,sizeof(f)); for(int i=1;i<=n;i++) f[1][i]=sum[i]; for(int k=2;k<=m;k++) for(int i=k;i<=n;i++) { f[k][i]=max(f[k-1][k-1],sum[i]-sum[k-1] ); for(int j=k+1;j<=i;j++) f[k][i]=min(f[k][i],max(f[k-1][j-1],sum[i]-sum[j-1] )); } int ans=(double)(sqrt((double)f[m][n]))*100; //printf("%d.%d",ans/100,ans%100);//错误写法:可能丢了1个前导0,最后没有两位小数 printf("%.2lf",ans/100.0); return 0; }
【2】逢低购买
先求最长的长度,以减少dp的复杂度
然后我想的是,记录f[k][i],以min(mx[i],mx[j]+1) * n* n的复杂度dp
但是!!!
可以压维成f[i],他的k是mx[i],并且这样是保证找到所有方案的!!!
直接不tle了
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> using namespace std; int n; const int N=5003; long long d[N]; int ans,mx[N]; struct hp { int len,d[100]; hp() { len=1; memset(d,0,sizeof(d)); } hp operator + (const hp & b ) const { hp ans; ans.len =max(len,b.len ); int jw=0; for(int i=1;i<=ans.len ;i++) { ans.d[i] =d[i] + b.d[i] +jw ; jw=ans.d[i] /10; ans.d[i] %=10; } if(jw) ans.d[++ans.len ]=jw; return ans; } }f[N]; void print(hp a) { for(int i=a.len ;i;i--) printf("%d",a.d[i] ); } long long a[N]; void get_mx_time() { int len=0; for(int i=1;i<=n;i++) { int pos=lower_bound(a+1,a+len+1,-d[i])-a; a[pos]=-d[i]; if(pos>len) len++; mx[i]=pos; } ans=len; } signed main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",&d[i]); get_mx_time(); printf("%d ",ans); f[1].len =1,f[1].d[1] =1; for(int i=2;i<=n;i++) { if(mx[i]==1) f[i].len =1,f[i].d[1] =1; for(int j=i-1;j;j--) if(d[j]>d[i] && mx[i]==mx[j]+1 ) f[i]=f[i]+f[j]; else if(d[j]==d[i] && mx[i]==mx[j] ) { f[i].len =1,f[i].d[1] =0; break; } } hp tmp; for(int i=ans;i<=n;i++) if(mx[i]==ans) tmp=tmp+f[i]; print(tmp); return 0; }
但是UVA的原题需要高精度,虽然我不知道是怎么看出来的,
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> using namespace std; int n; const int N=5003; int d[N]; int mx[N]; struct hp { int len,d[100]; hp() { mem(); } void mem() { memset(d,0,sizeof(d)); len=1; } hp operator + (const hp b ) const { hp ans; ans.len =max(len ,b.len ); int jw=0; for(int i=1;i<=ans.len ;i++) { ans.d[i] =d[i] + b.d[i] +jw ; jw=ans.d[i] /10; ans.d[i] %=10; } if(jw) ans.d[++ans.len ]=jw; return ans; } }f[N]; void print(hp a) { for(int i=a.len ;i>0;i--) printf("%d",a.d[i] ); printf(" "); } int a[N]; int get_mx_time() { int len=0; for(int i=1;i<=n;i++) { int pos=lower_bound(a+1,a+len+1,-d[i])-a; a[pos]=-d[i]; if(pos>len) len++; mx[i]=pos; } return len; } signed main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&d[i]); int ans=get_mx_time(); printf("%d ",ans); for(int i=1;i<=n;i++) { if(mx[i]==1) f[i].len =1,f[i].d[1] =1; for(int j=i-1;j>0;j--)//碰到最右边的一个相等的量,才清空 if(d[j]>d[i] && mx[i]==mx[j]+1 ) f[i]=f[i]+f[j]; else if(d[j]==d[i] && mx[i]==mx[j] ) f[j].mem() ; } hp tmp; tmp.mem(); for(int i=ans;i<=n;i++) if(mx[i]==ans) tmp=tmp+f[i]; print(tmp); return 0; }
【3】花店橱窗布置
很顺手的写出了方程,然后掉了初始化!!!
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int n,m; const int N=103,M=103;//花瓶数量,花的数量 int g[N][M],f[N][M],pre[N][M];//花瓶对应花 void get_plan(int i,int j) { if(j>1) get_plan(pre[i][j]-1,j-1); printf("%d ",pre[i][j]); } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) scanf("%d",&g[j][i]); memset(f,128,sizeof(f)); //先更新i-1号花瓶装0-i朵花 for(int i=1;i<=n;i++)//第i号花瓶 { f[i][0]=0; for(int j=1;j<=i;j++) if(f[i-1][j-1]+g[i][j] > f[i-1][j] ) f[i][j]=f[i-1][j-1]+g[i][j],pre[i][j]=i;//我装花的状态从i-1,j-1来,所以我想装花,要i-1,j-1状态为0, //如果不将f[0][0]=0,会导致1号花瓶不能装花 else f[i][j]=f[i-1][j],pre[i][j]=pre[i-1][j]; } printf("%d ",f[n][m]); get_plan(n,m); return 0; }
【4】迎接仪式
感谢luogu题解区大佬:
然后是我的思路:
(交换型dp get)
//最后一维表示最后一个字母是什么 //a[i]=='j' //前面是'j',后面可以换成'z' // f[nn-1][i][j-1][0] // 不换的时候,f[nn-1][i][j][1] //前面是'z',前后调换 // f[nn-1][i-1][j-1][1] +1 // 不换的时候,f[nn-1][i][j][1] //a[i]=='z' //前面是'j',不用换直接加 // f[nn-1][i][j][0] +1 //前面是'z',把前面换成'j' // f[nn-1][i-1][j][1] // 不换的时候,f[nn-1][i][j][1] #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> using namespace std; int n,K; const int N=503,M=103; char ch[503]; int d[N],f[N][M][M][2]; int main() { scanf("%d%d",&n,&K); scanf("%s",ch+1); for(int i=1;i<=n;i++) if(ch[i]=='z') d[i]=1; memset(f,128,sizeof(f)); f[0][0][0][1]=0; for(int nn=1;nn<=n;nn++) for(int i=0;i<=K;i++)//前面通过交换多了几个'j' for(int j=0;j<=K;j++)//多了几个'z' if(!d[nn] )//'j' { f[nn][i][j][0]=max(f[nn-1][i][j][1],f[nn-1][i][j][0]); if(j>0) f[nn][i][j][1]=max(f[nn-1][i][j-1][0]+1,f[nn-1][i][j-1][1]); } else { f[nn][i][j][1]=max(f[nn-1][i][j][0]+1,f[nn-1][i][j][1]); if(i>0) f[nn][i][j][0]=max(f[nn-1][i-1][j][0],f[nn-1][i-1][j][1]); } int ans=0; for(int i=0;i<=K;i++) ans=max(ans,max(f[n][i][i][0],f[n][i][i][1])); printf("%d ",ans); return 0; }
【5】环形最大连续子序列
神奇O(n)解法
以后碰到可能从中间断开的环,求极值,都可以从从最大最小的差来思考!!!
【6】最大联通子树和
#include<cstdio> #include<cstdlib> #include<queue> using namespace std; int n,ans1,ans2; const int N=5e4+3; int d[N],a[N]; int work()//求数组中最大连续子段和 { int as=-200; int pre=0,nw; for(int i=1;i<=n;i++) { nw=a[i]; if(pre>0) nw+=pre; pre=nw; as=max(as,pre); } return as; } void dp1()//环形 最大 连续子段和 的神奇O(n)求法 { int sum=0; for(int i=1;i<=n;i++) a[i]=d[i],sum+=d[i]; ans1=work(); for(int i=1;i<=n;i++) a[i]=-a[i]; ans1=max(ans1,sum+work()); printf("%d ",ans1); } int head[N],tot; int ev[N<<1],enx[N<<1]; void add(int u,int v) { ev[++tot]=v,enx[tot]=head[u],head[u]=tot; ev[++tot]=u,enx[tot]=head[v],head[v]=tot; } int f[N];//强制包括rt,和rt的子树的最大联通子树和 void dfs(int rt,int fa) { f[rt]=d[rt]; for(int i=head[rt];i;i=enx[i]) { if(ev[i]==fa) continue; dfs(ev[i],rt); if(f[ev[i]]>0) f[rt]+=f[ev[i]]; } ans2=max(ans2,f[rt]); } void dp2() { int u,v; for(int i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v); dfs(1,0); printf("%d ",ans2); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&d[i]); dp1(); dp2(); return 0; }