2021.03.20【NOIP提高B组】模拟 总结

区间 DP 专场:愉快爆炸

T1

题目大意

\(n\) 个有颜色的块,连续 \(k\) 个相同颜色的就可以消掉

现在可以在任意位置插入任意颜色的方块,问最少插入多少个可以全部抵消

题解

先把连续的化成一块,问题变为如何消掉一块。 \(num\) 为个数, \(color\) 为颜色

\(F_{i,j,len}\) 表示表示 \([i,j]\) 后面有 \(len\) 个和 \(j\) 颜色一样的与 \(j\) 一起消除。

初始 \(F_{i,i,len}=\max(0,k-num_i-len)\)

然后 \(F_{i,j,len}=F_{i,j-1,0}+\max(0,k-num_j-len)\) 即单独消去

枚举中转点 \(k'\)\(F_{i,j,len}=\min_{k'=i}^{j-1}F_{i,k',\min(k,len+num_j)}+F_{k+1,j-1,0} (color_j=color_{k'})\)

就是先消掉 \([k+1,j-1]\) 然后再将第 \(j\) 块与 \(i,k\) 一起操作

目标: \(F_{1,n,0}\)

#include<bits/stdc++.h>
using namespace std;
const int N=105; 
int n,K,x[N],cl[N],w[N],tot;
int f[N][N][10];
int main() {
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;i++)scanf("%d",&x[i]);
	cl[1]=x[1],w[1]=tot=1;
	for(int i=2;i<=n;i++) {
		if(x[i]^x[i-1]) {
			++tot,cl[tot]=x[i],w[tot]=1;
		} else ++w[tot];
	}
	memset(f,100,sizeof(f));
	n=tot;
	for(int i=1;i<=n;i++)
		for(int l=0;l<=K;l++)
			f[i][i][l]=max(0,K-w[i]-l);
	for(int l=2;l<=n;l++)
		for(int i=1,j=l;j<=n;i++,j++)
			for(int le=0;le<=K;le++) {
				f[i][j][le]=f[i][j-1][0]+max(0,K-w[j]-le);
				for(int k=i;k<j;k++)
					if(cl[j]==cl[k])
						f[i][j][le]=min(f[i][j][le],f[i][k][min(le+w[j],K)]+f[k+1][j-1][0]);
			}
	printf("%d",f[1][n][0]);
	return 0;
}

总结

这道题想到了 \(\text{dp}\) 却没有想到区间,更没有想到多设 \([len]\) 这一维

下次要学会通过多一个状态处理转移的错误

T2

题目大意

给你 \(n\)\(a_i\times b_i\) 的矩阵,问乘在一起最少要多少次乘法

输入确保能够相乘

题解

因为输入确保能够相乘,其实就是一个区间 \(\text{dp}\)

枚举中转点 \(k\) 时,这个区间 \([i,j]\) 的贡献是 \(x_i\cdot y_k\cdot y_j\)

#include<bits/stdc++.h>
using namespace std;
const int N=505;
int n,x[N],y[N],f[N][N];
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]);
	for(int l=2;l<=n;l++)
		for(int i=1,j=l;j<=n;i++,j++) {
			f[i][j]=2100000000;
			for(int k=i;k<=j;k++)
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+x[i]*y[k]*y[j]);
		}
	printf("%d",f[1][n]);
}

总结

这题以为是贪心,因为没有认真想提示的含义

T3

题目大意

给你一圈数,取一个数的贡献是这个数和其左边、右边的数的积

最后拿完只有两个数,问最大得分和最小得分的差

题解

  1. 断环成列
  2. 枚举区间 \([i,j]\) 中的最后取得数 \(k\in[i,j)\) 那么贡献为 \(a_i\cdot a_{k+1}\cdot a_{j+1}\)
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,x[N],f[N][N],g[N][N];
int mx,mn=2100000000;
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&x[i]),x[i+n]=x[i];
	for(int l=2;l<=n;l++) {
		for(int i=1,j=l;j<=(n<<1);i++,j++) {
			f[i][j]=2100000000;
			for(int k=i;k<j;k++) {
				g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+x[i]*x[k+1]*x[j+1]);
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+x[i]*x[k+1]*x[j+1]);
			}
		}
	}
	for(int i=1;i<=n;i++)
		mx=max(mx,g[i][i+n-2]),
		mn=min(mn,f[i][i+n-2]);
	printf("%d",mx-mn);
}

总结

这题不知道如何计算贡献,多想想状态的含义

T4

题目大意

\(n\) 个整数组成的圆环,\(i,j\) 的距离为两个格子之间的格子数的最小值

有一个开始指向 1 的指针,取一个数的代价即这个数的值

开始可取下指针所指的那个数以及与这个格子距离小于 \(k\) 的数

可以转动,每次转由一个格子转向其相邻的格子,且代价为圆环上还剩下的数的最大值

题解

所有各自都是要取得,至少需要 \(\sum_{i=1}^n a_i\)

开始可以取走距离小于 \(k\) 的,就是 \([1,k+1],[k,n]\) 全都没了

问题变成一个取完所有数的最小花费,用区间 \(DP\)

\(f_{i,j}\) 为从 \(i\) 取到 \(j\) 的最小话费,所以 \(f_{i,j}\ne f_{j,i}\)

区间最大可以预处理为 \(mx_{i,j}\)

\(f_{i,j}=\min(f_{i+1,j}+mx_{i,j},f_{j-1,i}+(len[1,i]+len[j,n])*mx_{i,j})\)

\(f_{i,j}=\min(f_{i-1,j}+mx_{i,j},f_{j+1,i}+(len[1,j]+len[i,n])*mx_{i,j})\)

就是一种正来一种反来

#include<bits/stdc++.h>
using namespace std;
const int N=2005;
int n,K,ans,x[N],f[N][N],mx[N][N];
int main() {
	scanf("%d%d",&n,&K);
	memset(f,100,sizeof(f));
	for(int i=1;i<=n;i++) {
		scanf("%d",&x[i]);
		ans+=x[i];
		mx[i][i]=f[i][i]=x[i];
	}
	if(n<=K+K+1)return printf("%d",ans),0;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			mx[i][j]=mx[j][i]=max(mx[i][j-1],x[j]);
	for(int l=1;l<n-K-K;l++) {
		for(int i=K+2,j=i+l;j<=n-K;i++,j++)
			f[i][j]=min(f[i+1][j]+mx[i][j],f[j-1][i]+(i-K-1 + n-K-j)*mx[i][j]);
		for(int i=n-K,j=i-l;j>=K+2;i--,j--)
			f[i][j]=min(f[i-1][j]+mx[i][j],f[j+1][i]+(j-K-1 + n-K-i)*mx[i][j]);
	}
	ans+=min(f[n-K][K+2],f[K+2][n-K]);
	printf("%d",ans);
}

总结

这道题确实是比较好,要学会转换问题

End

这次区间 \(dp\) 给本蒟蒻敲响警钟,要注意了

原文地址:https://www.cnblogs.com/KonjakLAF/p/14583981.html