• 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\) 给本蒟蒻敲响警钟,要注意了

  • 相关阅读:
    MyCat 数据库读写分离
    python 网络编程-05 socketserver
    python 网络编程-04 文件的传输
    python 网络编程-03 粘包问题及处理
    常用模块-01getopt
    js获取自动获取本电脑IP
    js变态需求
    js正则不能输入中文和英文
    禁止输入中文
    手机号3-4-5
  • 原文地址:https://www.cnblogs.com/KonjakLAF/p/14583981.html
Copyright © 2020-2023  润新知