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

浙公网安备 33010602011771号