洛谷 P2703 [NOI2006]千年虫 题解

题目链接

题意简述

好像是删格子,但是不难发现这个千年虫把格子分成了左右两部分,我们不考虑千年虫,题目就是在左右两边加上一些格子,是左右两边都是梳子形状,求最少加几个格子。

具体做法

因为左右两边相对独立,所以可以单独计算两边。

设第i行的初始高度为 \(a_i\);

最终高度为 \(b_i\);

\(f(i,j,s)\) 表示前i行中当前行最终高度为 \(j\),凹凸性为 \(s\)(\(s=0\) 是凹,\(s=1\) 是凸)所需要加的最少块数。

可得:
\(f(i,j,s)=\min\{f(i-1,j-1,s),f(i-1,k,1-s)\}(k<j,s=1;k>j,s=0)\);

这样的复杂度是 \(O(n^3)\) 的,大部分测试点肯定会超时,可以预处理一下,然后复杂度就会变为 \(O(n^2)\),应该可以得到一半的分。

一半分的代码我就不放了。

那么如何才能拿到满分呢?

我们发现,每行的最优值只会在所有的开区间 \(a_{p},a_{p}+2(\left\vert p-i \right\vert \le 2)\) 中产生,其实在我们可以证得无论两边的大小于关系是否大于二,都会满足这个性质(详细见下),用它来优化 \(j\) 的枚举边界,\(j\) 的有限个状态可以让复杂度变到 \(O(n)\)

小于时最优值 \(b_{i}\) 会满足 \(\left\vert p-i \right\vert \le 1,b_{p}\in a_{p},a_{p}+2\);

大于时会满足 \(\left\vert p-i \right\vert \le 2,b_{p}\in a_{p},a_{p}+2\);

所以满足以上结论。

然后就可以愉快的过题了!

100pts code

/*
这次不卡常也跑得飞快!
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000001,INF=0x3f3f3f3f;
int n,ans,l[maxn],a[maxn],f[2][20][2],q[2][maxn],p[2];
void work()
{
	memset(f,0x7f,sizeof(f));
	p[1]=0;
	for(int j=1;j<4;++j)
	{
		for(int k=a[j];k<a[j]+3;++k)
		{
			if(k>=a[1]) q[1][++p[1]]=k;
		}
	}
	for(int i=1;i<=p[1];++i)
	{
		f[1][i][0]=q[1][i]-a[1];
	}
    int u=1,v=0;
    for(int i=2;i<n+1;++i)
    {
        u^=1;v^=1;
		p[u]=0;
       	int x=(i-2<1)?1:i-2,y=(n<i+2)?n:i+2;
       	for(int j=x;j<y+1;j++)
       	{
       		for(int k=a[j];k<a[j]+3;k++)//根据以上性质优化
       		{
       			if(k>=a[i]) q[u][++p[u]]=k;	
			}
		}
		for(int j=1;j<=p[u];++j)
		{
			f[u][j][1]=f[u][j][0]=INF;
			for(int k=1;k<=p[v];++k)
			{
				if(q[v][k]>q[u][j]) f[u][j][0]=min(f[u][j][0],f[v][k][1]);
				else
				{
					if(q[v][k]<q[u][j]) f[u][j][1]=min(f[u][j][1],f[v][k][0]);
					else
					{
						f[u][j][0]=min(f[u][j][0],f[v][k][0]);
						f[u][j][1]=min(f[u][j][1],f[v][k][1]);
					}
				}	
			}
			f[u][j][0]+=q[u][j]-a[i];
			f[u][j][1]+=q[u][j]-a[i];
		}
	}
	int tmp=INF;
	for(int i=1;i<=p[u];++i)
	{
		tmp=min(tmp,f[u][i][0]);
	}
	ans+=tmp;
}
signed main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d",&l[i],&a[i]);
	}
	work();//同样是分左右两部分操作
	for(int i=1;i<=n;++i)
	{
		a[i]=maxn-l[i];
	}
	work();
	printf("%d",ans);
	return 0;
}

思路来自这里

posted @ 2022-09-18 20:43  离弦  阅读(78)  评论(0编辑  收藏  举报