luogu P6764 [APIO2020]粉刷墙壁

题面传送门
仔细阅读题面可知,只有在这个循环矩阵内有连续 k k k个斜着的 1 1 1,那么就可以覆盖。
所以这题可以分成两部分:预处理哪些区间可以被覆盖和处理覆盖的最小区间。
处理覆盖的最小区间显然可以单调队列优化dp,设 f i f_i fi为覆盖到第 i i i个且最后一个矩阵右端点在 i i i处的最小覆盖数量。那么可得状态转移方程为 f i = max ⁡ j = max ⁡ ( 0 , i − m ) j < i f j + 1 [ p i ] f_i=\max\limits_{j=\max(0,i-m)}^{jfi=j=max(0,im)maxj<ifj+1[pi]其中 p i p_i pi表示这个区间能否被覆盖。
由于左端点单调不降所以可以单调队列优化。复杂度 O ( n ) O(n) O(n)
那么关键是预处理部分。
子任务 1 1 1:只要判断一下颜色数组中是否连续即可。
子任务 2 2 2:用 m a p map map保存第 i i i个承包商能否填涂第 j j j个颜色。暴力求解。
子任务 3 3 3:用 m a p map map保存第 i i i个承包商能否填涂第 j j j个颜色。在暴力上做一个小优化,比如当前节点如果 ≥ m \geq m m就继续并保存而不是跳出。
子任务 4 4 4:我们知道 m a p map map常数很大那么可以用 b i t s e t bitset bitset替换。就能勉强卡过去。
子任务 5 5 5:这么大的空间 b i t s e t bitset bitset是开不下的。可以考虑用递推来优化暴力。
c i c_i ci为第 i i i个承包商的可以填涂的颜色的集合。设 d p i , j dp_{i,j} dpi,j为当前到了第 i i i个承包商,结尾颜色为 j j j最大的长度。那么递推式就是 d p i , j = d p i − 1 , ( j − 1 + m ) % m + 1 [ j ∈ c i ] dp_{i,j}=dp_{i-1,(j-1+m)\%m}+1[j∈c_i] dpi,j=dpi1,(j1+m)%m+1[jci],若有一个位置 ≥ m \geq m m那么 p i = 1 p_i=1 pi=1
这样的时空复杂度都是 O ( n k ) O(nk) O(nk)的。
空间复杂度可以用滚动数组优化成 O ( n ) O(n) O(n),对于一个 i i i,只有 j ∈ c i j∈c_i jci的才是有效的,所以可以用一个 v e c t o r vector vector把这些 j j j存起来,题目中保证 ∑ i = 1 k f ( i ) ≤ 4 × 1 0 5 \sum\limits_{i=1}^{k}{f(i)}\leq 4\times10^5 i=1kf(i)4×105,所以复杂度最大为 O ( n ∑ i = 1 k f ( i ) ) O(n\sqrt{ \sum\limits_{i=1}^{k}{f(i)}}) O(ni=1kf(i) ),实际数据并没有这么满,完全可过。
代码实现(考场代码,略微难看,不喜勿喷):

#include "paint.h"
#include 
#include 

#include 
#include
#include
#include
using namespace std;
int n,m,k,x,y,z,dp[200039],a[200039],t[200039],pl[200039],head,tail,q[200039],flag,f[5][200039],now,last;
vector<int> fs[200039];
int minimumInstructions(
    int N, int M, int K, std::vector<int> C,
    std::vector<int> A, std::vector<std::vector<int> > B) {
     
    n=N;m=M;k=K;
	register int i,j;
	for(i=1;i<=n;i++)a[i]=C[i-1];
	for(i=1;i<=m;i++){
     
		t[i]=A[i-1];
		for(j=1;j<=t[i];j++) fs[B[i-1][j-1]].push_back(i);
	}
	for(i=1;i<=n;i++){
     
		now=i&1;last=now^1;
		if(i>=3) for(j=0;j<fs[a[i-2]].size();j++) f[now][fs[a[i-2]][j]]=0;
		for(j=0;j<fs[a[i]].size();j++){
     
			f[now][fs[a[i]][j]]=f[last][(fs[a[i]][j]-2+m)%m+1]+1;
			if(f[now][fs[a[i]][j]]>=m) pl[i]=1;
		}
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;head=tail=1;
	for(i=1;i<=n;i++){
     
		while(head<=tail&&q[head]<i-m) head++;
		if(pl[i])dp[i]=dp[q[head]]+1;
		while(head<=tail&&dp[q[tail]]>=dp[i]) tail--;
		q[++tail]=i;
	}
	if(dp[n]>=1e9) return -1;
	return dp[n];
}

你可能感兴趣的:(洛谷,动态规划,单调队列,递推)