• 【LsWn的动态规划】区间DP


    区间 DP

    一般基础状态:\(f(l,r)\) 表示区间为 \([l,r]\) 的答案,然后进行转移。

    由于我比较懒,在不需要太多优化时喜欢写递归的区间 \(dp\)

    int dp(int l,int r){
    	if(f[l][r]) return f[l][r];
    	if(l==r) return f[l][r]==...;
    	for(int k=...;k<=...;k++) f[l][r]=...;
    	return f[l][r];
    }
    

    或者

    int dp(int l,int r){
    	if(f[l][r]) return f[l][r];
    	if(l==r) return f[l][r]==...;
    	f[l][r]=...(f[l+1][r],f[l][r-1],...);
    	return f[l][r];
    }
    

    能量项链

    自然状态 \(f(l,r)\),破环成链。

    \[ f(l,r)=\max\limits_{l\le k<r}{f(l,k)+f(k+1,r)+a_la_ra_k} \]

    #include<bits/stdc++.h>
    using namespace std;
    const int N=109;
    int n,a[N*3],f[N*2][N*2],ans;
    int dfs(int u,int v){
    	if(f[u][v]) return f[u][v];
    	if(u==v) return f[u][v]=0;
    	for(int k=u;k<v;k++)f[u][v]=max(f[u][v],dfs(u,k)+dfs(k+1,v)+a[u]*a[v+1]*a[k+1]);
    	return f[u][v];
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+2*n]=(a[i+n]=a[i]);
    	for(int i=1;i<=n;i++) ans=max(ans,dfs(i,i+n-1));
    	printf("%d",ans);
    	return 0;
    }
    

    石子合并

    自然状态,破环成链。 然后这题要算两次。

    \(f\) 表示最大值。

    \[f(l,r)=\max\limits_{l\le k<r}{f(l,k)+f(k+1,r)+\sum\limits_{i=l}^{r}a_i} \]

    #include<bits/stdc++.h>
    using namespace std;
    const int N=209;
    int n,a[N],s[N],f[N][N],g[N][N],ans1,ans2=0x3f3f3f3f;
    int dfs1(int l,int r){
    	if(f[l][r]) return f[l][r];
    	if(l==r) return f[l][r]=0;
    	for(int k=l;k<r;k++) f[l][r]=max(f[l][r],dfs1(l,k)+dfs1(k+1,r)+s[r]-s[l-1]); 
    	return f[l][r];
    }
    int dfs2(int l,int r){
    	if(g[l][r]!=0x3f3f3f3f) return g[l][r];
    	if(l==r) return g[l][r]=0;
    	for(int k=l;k<r;k++) g[l][r]=min(g[l][r],dfs2(l,k)+dfs2(k+1,r)+s[r]-s[l-1]); 
    	return g[l][r];
    }
    int main(){
    	scanf("%d",&n); memset(g,0x3f,sizeof(g));
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+n]=a[i];
    	for(int i=1;i<=2*n;i++) s[i]=s[i-1]+a[i];
    	for(int i=1;i<=n;i++) ans1=max(ans1,dfs1(i,i+n-1)),ans2=min(ans2,dfs2(i,i+n-1));
    	printf("%d\n%d",ans2,ans1);
    	return 0;
    }
    

    USACO16 248G

    自然状态,要判断是否相等。

    \[f(l,r)=\max\limits_{l\le k<r,f(l,k)=f(k+1,r)}{f(l,k)+1} \]

    #include<bits/stdc++.h>
    #pragma optimize("Ofast,unroll-loop")
    using namespace std;
    const int N=259,ninf=-1e9;
    int n,f[N][N],a[N],ans;
    int dfs(int u,int v){
    	if(f[u][v]) return f[u][v]; f[u][v]=ninf;
    	if(u==v) return f[u][v]=a[u];
    	for(int k=u;k<=v-1;k++)
    		if(dfs(u,k)==dfs(k+1,v)) f[u][v]=max(f[u][v],dfs(u,k)+1);
    	return ans=max(ans,f[u][v]),f[u][v];
    }
    int main(){
    	scanf("%d",&n);
    	for(register int i=1;i<=n;++i) scanf("%d",&a[i]);
    	dfs(1,n),printf("%d",ans);
    	return 0;
    }
    

    USACO16 262144P

    上题加强版,要做一个优化。

    如果枚举 \(l,r\) 就需要枚举中间点 \(k\),是件非常耗时间的事情。考虑到其实合并的答案很小,我们答案状态转换,即 \(f(l,p)\) 表示 左端点为 \(l\),答案为 \(p\) 的右端点(可证明右端点唯一)

    \[f(l,p)=f(f(l,p-1),p-1) \]

    按照倍增的循环方式,先循环 \(p\),再做 \(l\)

    #include<bits/stdc++.h>
    using namespace std;
    int n,a[262525],f[262525][65],ans;
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i][a[i]]=i+1;
    	for(int p=1;p<=59;p++){
    		for(int l=1;l<=n;l++){
    			if(!f[l][p]) f[l][p]=f[f[l][p-1]][p-1];
    			if(f[l][p]) ans=max(ans,p);
    		}
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    [USACO06FEB]Treats for the Cows G/S

    普通状态,第二种转移。一个水题。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2009;
    int n,a[N],s[N],f[N][N];
    int dp(int l,int r){
    	if(f[l][r]) return f[l][r];
    	if(l==r) return f[l][r]=a[l];
    	f[l][r]=max(dp(l,r-1),dp(l+1,r))+s[r]-s[l-1];
    	return f[l][r];
    }
    int main(){
    	scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
    	printf("%d",dp(1,n));
    	return 0;
    }
    

    [CQOI2007] 染色

    朴素状态。

    转移采取一个稍微贪心的思想:如果 \(l,r\) 颜色一样,那么可以先不管其中一个端点,然后去做,即 \(f(l,r)=\min(f(l+1,r),f(l,r-1))\),因为我们本来就是一开始把这个区间全部染成这个相同的颜色,能染一次就满足首尾的颜色。

    \[f(l,r)=\min\limits_{l\le k<r} f(l,k)+f(k+1,r) \]

    如果首尾相同:

    \[f(l,r)=\min(f(l+1,r),f(l,r-1)) \]

    #include<bits/stdc++.h>
    using namespace std;
    const int N=109,inf=0x3f3f3f3f;
    int n,f[N][N]; char a[N];
    int dp(int l,int r){
    	if(f[l][r]!=inf) return f[l][r];
    	if(l==r) return f[l][r]=1;
    	if(a[l]==a[r]) return f[l][r]=min(dp(l+1,r),dp(l,r-1));
    	for(int k=l;k<r;k++) f[l][r]=min(f[l][r],dp(l,k)+dp(k+1,r));
    	return f[l][r];
    }
    int main(){
    	memset(f,0x3f,sizeof(f));
    	scanf("%s",a+1); n=strlen(a+1);
    	printf("%d",dp(1,n));
    	return 0;
    } 
    

    [IOI1998] Polygon

    朴素状态。

    对于加法,肯定是选择两个大的相加。

    对于乘法,可能遇到两个负数相乘边为正数然后碾压正数的情况。对此,我们记录最小值,每次拿 \(4\) 个状态
    (即

    \[f(l,k)g(r+1,k),f(l,k)g(k+1,r),g(l,k)g(k+1,r),g(l,k)f(k+1,r) \]


    去比较。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=509,inf=1e18;
    char e[N]; int n,a[N],f[N][N],g[N][N],mx,ans[N];
    int dp(int l,int r){
    	if(f[l][r]!=-inf) return f[l][r];
    	if(l==r) return f[l][r]=g[l][r]=a[l];
    	for(int k=l;k<r;k++){
    		dp(l,k),dp(k+1,r);
    		if(e[k+1]=='t')
    			f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]),g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]);
    		else
    			f[l][r]=max(f[l][r],max(f[l][k]*f[k+1][r],
    				max(g[l][k]*g[k+1][r],max(f[l][k]*g[k+1][r],g[l][k]*f[k+1][r])))),
    				
    			g[l][r]=min(g[l][r],min(g[l][k]*g[k+1][r],
    				min(f[l][k]*f[k+1][r],min(f[l][k]*g[k+1][r],g[l][k]*f[k+1][r]))));
    	}
    	return f[l][r];
    }
    signed main(){
    	scanf("%lld",&n);
    	for(int i=1;i<=2*n;i++) for(int j=1;j<=2*n;j++) f[i][j]=-inf,g[i][j]=inf;
    	for(int i=1;i<=n;i++) cin>>e[i]>>a[i],e[i+n]=e[i],a[i+n]=a[i];
    	for(int i=1;i<=n;i++){
    		ans[i]=dp(i,n+i-1); mx=max(mx,ans[i]);
    	}
    	printf("%lld\n",mx);
    	for(int i=1;i<=n;i++) if(ans[i]==mx) printf("%lld ",i);
    	return 0;
    }
    

    [HNOI2010] 合唱队

    如果用自然状态,我们会发现无法确定现在应该选左边还是右边(因为受到上一个选的影响)。

    于是我们定义 \(f(l,r,0/1)\)\([l,r]\) 中现在取左/右。

    对于 \(l=r\) 的情况,由于只能算一次,于是我们规定 \(l=r\) 的话只能选右边(只能选左边也行)。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1009,mod=19650827;
    int n,a[N],f[N][N][2],v[N][N][2],tick;
    int dp(int l,int r,int status){
    	if(v[l][r][status]) return f[l][r][status]; v[l][r][status]=1;
    	if(l==r) return f[l][r][status]=!status;
    	if(status==0){
    		if(a[l]<a[l+1]) f[l][r][0]+=dp(l+1,r,0);
    		if(a[l]<a[r]) f[l][r][0]+=dp(l+1,r,1);
    	}else{
    		if(a[r]>a[r-1]) f[l][r][1]+=dp(l,r-1,1);
    		if(a[r]>a[l]) f[l][r][1]+=dp(l,r-1,0);
    	} return f[l][r][status]%=mod;
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	printf("%d\n",(dp(1,n,0)+dp(1,n,1))%mod);
    	return 0;
    }
    

    [USACO19DEC]Greedy Pie Eaters P

    自然状态

    \[f(l,r)=\max(\max f(l,k)+f(k+1,r),\max f(l,k-1)+f(k=1,r)+p(k,l,r)) \]

    其中 \(p\) 表示吃的在 \(l,r\) 区间内包括 \(k\) 点的最大体重的牛的体重。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=309;
    int n,m,w,l,r,p[N][N][N],f[N][N];
    bool vst[N][N][N],v[N][N];
    int dpp(int k,int l,int r){
    	if(vst[k][l][r]) return p[k][l][r]; vst[k][l][r]=1;
    	if(l==r) return p[k][l][r];
    	if(r<k||l>k) return 0;
    	
    	if(r>1) p[k][l][r]=max(p[k][l][r],dpp(k,l,r-1));
    	if(l<n) p[k][l][r]=max(p[k][l][r],dpp(k,l+1,r));
    	return p[k][l][r];
    } 
    int dp(int l,int r){
    	if(v[l][r]) return f[l][r]; v[l][r]=1;
    	if(l>r) return 0;
    	for(int k=l;k<r;k++) f[l][r]=max(f[l][r],dp(l,k)+dp(k+1,r));
    	for(int k=l;k<=r;k++){
    		f[l][r]=max(f[l][r],dp(l,k-1)+dp(k+1,r)+p[k][l][r]);
    	}
    	return f[l][r];
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&w,&l,&r);
    		for(int j=l;j<=r;j++) p[j][l][r]=w;
    	}
    	for(int k=1;k<=n;k++) dpp(k,1,n);
    	printf("%d",dp(1,n));
    	return 0;
    }
    
  • 相关阅读:
    老罗锤子手机发布会,我感到深深地愧疚!
    微价值:专访《甜心爱消除》的个人开发者Lee,日入千元
    [个人开发者赚钱二]从自己最熟悉的方面入手,获取小利
    [个人开发者赚钱一]改变思维,从心开始
    个人开发者赚钱一、改变思维,从心开始
    OC中的点语法,成员变量的作用域
    OC self super isa指针
    OC面向对象多态笔记
    OC面向对象继承关系和组合关系笔记
    OC面向对象封装
  • 原文地址:https://www.cnblogs.com/TetrisCandy/p/12810607.html
Copyright © 2020-2023  润新知