题目
思路
首先这是一个 d p \tt dp dp 问题。那么我们需要找到递归子问题。
一般会枚举某个特值的情况。比如最后一个灯笼。考虑它覆盖了哪些灯笼?那未被照亮的灯笼是离散的,不行。那么可以考虑它被谁覆盖。
不妨设灯笼 j j j 覆盖了它。那么 ( j , n ] (j,n] (j,n] 灯笼都是照亮左边灯笼,被照亮的灯笼继续往左照亮,直到某个 p i = 0 p_i=0 pi=0 停止(照不到左侧)。此时只剩下 [ 1 , i ) [1,i) [1,i) 的灯笼没照亮,看看它们是否可以内部自行照亮即可。
这样是 O ( n 2 ) \mathcal O(n^2) O(n2) 的。注意到 d p \tt dp dp 值都是 b o o l \tt bool bool 类型,可以考虑将其改为 i n t \tt int int 类型,以此减小转移时所需处理的信息——显然就是用 i n t \tt int int 动态做到 “被照亮的灯笼继续往左照亮” 的过程,只需要存储被照亮的灯笼的数量即可。
当然这样也不用递归了,直接递推。完整地说一遍,记 f ( i ) f(i) f(i) 为前 i i i 个灯笼能照亮的最长前缀长度。转移则有两种,分别对应原来的枚举 j j j 和继续往后迭代照亮。
- 任取 j ⩾ i j\geqslant i j⩾i 满足 j − p j ⩽ f ( i − 1 ) + 1 j-p_j\leqslant f(i{\rm-}1)+1 j−pj⩽f(i−1)+1,则 f ( j ) f(j) f(j) 可用 max t = i j − 1 t + p t \max_{t=i}^{j-1}t+p_t maxt=ij−1t+pt 更新。特别地,若 j = i j=i j=i,用 ( j − 1 ) (j{\rm-}1) (j−1) 更新。
- 若 f ( i − 1 ) ⩾ i f(i{\rm-}1)\geqslant i f(i−1)⩾i,则 f ( i ) f(i) f(i) 可用 max { f ( i − 1 ) , i + p i } \max\{f(i{\rm-}1),i+p_i\} max{f(i−1),i+pi} 更新。
最有趣的是,第一类转移并不用到 f ( i − 1 ) f(i{\rm-}1) f(i−1) 来更新,因为它本来就是在无法继续照亮的时候,才会需要重新枚举 j j j 。从式子上来理解,就是只有 f ( i − 1 ) < i f(i{\rm-}1)<i f(i−1)<i 时需要考虑第一类转移,此时 t = i t=i t=i 就已经让 max \max max 取更大值了。
此时换用填表法。对于一个 j j j,其最优的 i i i 肯定越小越好;所以目标只是找到最小的 i i i 满足 f ( i − 1 ) ⩾ j − p j − 1 f(i{\rm-}1)\geqslant j-p_j-1 f(i−1)⩾j−pj−1 。单调栈上二分即可实现。查询最大值则可 S T \tt ST ST 表,或者任何数据结构。时间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。
代码
#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for LONELINESS)
#include <cctype> // ZXY yydSISTER!!!
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void getMax(int &x, const int &y){
if(x < y) x = y;
}
const int MAXN = 300005;
int p[MAXN];
namespace ST{
const int LOGN = 19;
int st[LOGN][MAXN], logtwo[MAXN];
void init(int n){
rep(i,2^(logtwo[1]=0),n) logtwo[i] = logtwo[i>>1]+1;
}
void build(int n){
rep(i,1,n) st[0][i] = i+p[i];
rep(j,0,LOGN-2) rep(i,1,n-(2<<j)+1)
st[j+1][i] = max(st[j][i],st[j][i+(1<<j)]);
}
inline int query(const int &l, const int &r){
const int &k = logtwo[r-l+1];
return max(st[k][l],st[k][r-(1<<k)+1]);
}
}
int best[MAXN], dp[MAXN], sta[MAXN], top;
void paint(int x){
if(!x) return ;
if(best[x] == -1){
paint(x-1), putchar('R');
return ; // covered
}
paint(best[x]);
rep(i,best[x]+1,x-1) putchar('R');
putchar('L'); // x is chosen to be left
}
bool cmp(const int &x, const int &y){
return dp[x] < y;
}
int main(){
ST::init(MAXN-1);
for(int T=readint(); T; --T){
int n = readint();
rep(i,1,n) p[i] = readint();
ST::build(n), sta[0] = top = 0;
for(int i=1; i<=n; ++i){
sta[top+1] = i; // force best[i] = i
dp[i] = (dp[i-1] >= i) ? max(dp[i-1],i+p[i]) : 0;
best[i] = *lower_bound(sta,sta+top+1,i-p[i]-1,cmp);
int got = (best[i] == i) ? -1 : ((best[i] == i-1) ?
i-1 : ST::query(best[i]+1,i-1)); // at least j-1
if(dp[i] < got) dp[i] = got; else best[i] = -1;
if(dp[sta[top]] < dp[i]) sta[++ top] = i;
}
if(dp[n] < n) puts("NO");
else puts("YES"), paint(n), putchar('\n');
}
return 0;
}