VP的时候打满了63分,没想到正解竟是两个暴力的合体/dk
首先,在这里定义粉刷匠的下家关系:
对于粉刷匠
i
,
j
i,j
i,j,如果
i
+
1
=
j
i+1=j
i+1=j 或者
i
=
m
,
j
=
1
i=m,j=1
i=m,j=1,则称
j
j
j 是
i
i
i 的下家
6分: f ( k ) ≤ 1 f(k)\le 1 f(k)≤1
可以发现,对于每一次操作,要么所有的粉刷匠都刷一格,要么都不刷。所以,我们尝试对于每一格,是否存在一种粉刷顺序使得可以刷完后面的 m m m 格。
设
f
i
f_i
fi 表示从第
i
i
i 格开始刷最多可以往后刷多远(包括第
i
i
i 格)
转移方程:
f
i
=
f
i
+
1
+
1
f_i=f_{i+1}+1
fi=fi+1+1
如果
i
+
1
i+1
i+1 不是
i
i
i 的下家,则
f
i
=
1
f_i=1
fi=1
时间复杂度 O ( n ) \mathcal O(n) O(n)
51分
上一档部分分考虑的是每个格子只能由一个粉刷匠刷,现在我们考虑把问题扩展到多个粉刷匠
设 f i , j f_{i,j} fi,j 表示位置 i i i 由编号为 j j j 的粉刷匠来刷,最多可以往后刷多少格(包括第 i i i 格)
转移方程:
f
i
,
j
=
f
i
+
1
,
k
f_{i,j}=f_{i+1,k}
fi,j=fi+1,k,这里
k
k
k 为
j
j
j 的下家
如果粉刷匠
j
j
j 不能刷第
i
i
i 格,则
f
i
,
j
=
0
f_{i,j}=0
fi,j=0
时空复杂度 O ( n m ) \mathcal O(nm) O(nm)
100分
观察上面51分的做法,可以发现他进行了许多冗余的操作:
对于位置
i
i
i,所有不能刷第
i
i
i 格的粉刷匠都被访问了一遍。
那么,我们可以预处理出每个位置可能被哪些粉刷匠刷,并且在转移时只转移可以刷第
i
i
i 格的粉刷匠
这样,时间复杂度就变成了
O
(
∑
f
(
k
)
)
\mathcal O(\sum f(k))
O(∑f(k))
注意这里的空间也也需要优化,可以使用滚动数组或者用 map
优化掉后一维,代码中采用的是后者
#include <vector>
#include<map>
using namespace std;
const int Maxn=1e5+10;
map <int,int> f[Maxn];
vector <int> c[Maxn],b[Maxn];
int a[Maxn];
bool flag[Maxn];
int n,m,k;
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;
for(int i=1;i<=n;++i)
a[i]=C[i-1]+1;
for(int i=1;i<=m;++i)
{
int tmp=A[i-1];
for(int j=0;j<tmp;++j)
b[B[i-1][j]+1].push_back(i);
}
for(int i=1;i<=n;++i)
c[i]=b[a[i]];
for(int i=n;i;--i)
for(int j=0;j<c[i].size();++j)
{
int x=c[i][j];
int y=c[i][j]+1;
if(y>m)y=1;
f[i][x]=1;
if(i+1<=n)f[i][x]=f[i+1][y]+1;
f[i][x]=min(f[i][x],m);
if(f[i][x]==m)flag[i]=1;
}
int ret=0;
for(int i=n-m+1;i;--i)
{
if(!flag[i])return -1;
++ret;
if(i==1)break;
int tmp=max(i-m,1);
for(int j=tmp;j<i;++j)
if(flag[j] || j==i-1){i=j+1;break;}
}
return ret;
}