QOJ5173 染色
涉及知识点:扫描线,贪心
前言
非常好的一道题,转化十分精彩,之前完全没想到能有接近线性的做法。
题意
有一串纸条,上有 个格子,每个格子最开始有某种初始颜色,你可以用一秒钟将某个格子染成任意颜色,或者向左向右移动,前提是移动前后的格子颜色相同。有 组询问,每次询问从 到 的最短时间,询问之间独立。
。
思路
转化
正推没有什么头绪,我们可以反过来想,移动 距离最多可能需要的时间为 ,其中 是移动操作,另外 是染色操作,表示每一次移动前都将当前格子染成下一个格子的颜色,不难用发现这样的策略,无论格子颜色如何都一定能够成功抵达终点。于是在这个基础上考虑如何使步数更小,我们发现如果其中有一对 满足 ,此时总步数就可以减小 ,我们可以将 的格子都染为 然后直接移动,这样从 移动到 就只需要染色 次,但按照我们之前的策略会染 次。
ABCA
之前的策略:
ABCA BBCA BBCA BCCA BCCA BCAA BCAA(共 步)
优化策略:
ABCA AACA AAAA AAAA AAAA AAAA(共 步)
子问题:拆子区间
因此我们的问题就转化为了:给定一段区间,求区间里最多能拆出多少子区间,满足子区间两端点颜色相同,并且子区间之间不交(端点可交)。
这听起来似乎有点经典,事实上,如果没有多次询问的限制,这是一个简单的线性 DP:
线性 DP:
记 为 时最大的 , 为全局最大的 即可简单优化为线性。
for(int i=1;i<=n;i++){ f[i]=max(maxf,g[a[i]]); maxf=max(maxf,f[i]); g[a[i]]=max(g[a[i]],f[i]+1); }
眼看 DP 是没什么办法优化下去了……但是我们发现,这题可以直接贪心!
怎么贪呢?对于一个点 ,它的下一个区间选择它右侧区间中右端点最小的区间(这个区间的左端点可以为 ,也可以大于 ,但不能小于 ),将这个区间的右端点记为 。不断从起点跳 ,直至再跳就要超过终点为止,跳的步数就是最大子区间数。
比如说, 的 数组为 。
为什么这样贪心对呢?因为 的设定相当于我们固定住了左边界 ,于是当然是右边界越靠左越优。
是可以线性求的,只需要先预处理出每个格子下一个与它颜色相同格子的编号 ,注意此处 是倒序遍历:
转移方程表示我们每遍历到一个格子 就用区间 来更新一下答案。
离线询问
最后需要解决的问题,就是怎么处理这么多组询问。我们可以扫描线,当前扫到 ,利用并查集维护某个点 能跳到的最大的 的点,于是一组 的询问的答案即为 跳多少次能跳到并查集的根,在求 时顺便用一个类似后缀和的东西维护一下即可,具体可以看代码的 suf
数组。
细节
还有一个小细节,原问题并未保证起点 终点 ,不过根据我们以上分析,显然 到 和 到 答案是一样的,直接交换起点终点就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1; ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
typedef pair<int,int> pii;
const int MAXN=1e6+5;
int n,q,a[MAXN],ans[MAXN];
int fa[MAXN],suf[MAXN],buck[MAXN],nxt[MAXN];
vector<int>son[MAXN];
struct QUERY{
int l,r,id;
QUERY(){}
QUERY(int a,int b,int c):l(a),r(b),id(c){}
};
vector<QUERY>qry[MAXN];
struct DSU{
int fa[MAXN];
int find(int x){
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void init(int len){
for(int i=1;i<=len;i++){
fa[i]=i;
}
}
}dsu;
int main(){
rd(n);rd(q);
for(int i=1;i<=n;i++){
rd(a[i]);
}
int x,y;
for(int i=1;i<=q;i++){
rd(x);rd(y);
if(x>y) swap(x,y);
qry[y].emplace_back(x,y,i);
}
dsu.init(n+1);
for(int i=n;i>=1;i--){
if(!buck[a[i]]) nxt[i]=n+1;
else nxt[i]=buck[a[i]];
buck[a[i]]=i;
}
fa[n+1]=n+1;suf[n+1]=-1;
for(int i=n;i>=1;i--){
fa[i]=min(fa[i+1],nxt[i]);
son[fa[i]].push_back(i);
suf[i]=suf[fa[i]]+1;
}
for(int i=1;i<=n;i++){
for(auto it:son[i]){
dsu.fa[dsu.find(it)]=i;
}
for(auto it:qry[i]){
ans[it.id]=2*(it.r-it.l+1)-2-(suf[it.l]-suf[dsu.find(it.l)]);
}
}
for(int i=1;i<=q;i++){
wt(ans[i],'\n');
}
return 0;
}
本文作者:MessageBoxA
本文链接:https://www.cnblogs.com/SkyNet-PKN/p/18528336
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2023-11-06 AGC027E ABBreviate