题解 P9120【[春季测试 2023] 密码锁】
题意
有一个 $k\times n$ 的矩阵,对于每一列,你可以任意旋转,求每行的极差的最大值的最小值。多组数据。
$T\ge 1$,$1\le a_{i,j}\le3\times10^4$,$1\le k\le 4$。
对于 $1\le k\le 3$,$1\le n\le 5\times 10^4$,$\sum n\le 1.5\times 10^5$。
对于 $k=4$,$1\le n\le 10^4$,$\sum n\le 3\times 10^4$。
思路
对每个 $k$ 处理。
$k=1$
直接求极差,时间复杂度 $O(\sum nk)$。
$k=2$
考虑将小的数放上面,大的数放下面。
思考一下这样为什么是对的?考虑全局最小值和全局最大值所在的行,显然放在两行不劣于放在同一行,设最小值放上面,最大值放下面,那么要想让上面的数尽量小,下面的数尽量大,就是上面的方法。
时间复杂度 $O(\sum nk)$。
namespace Sub12{
const int N=5e4+10;
int n,a[3][N];
inline void main(){
while(T--){
n=read();
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)
a[i][j]=read();
if(k==2){
for(int i=1;i<=n;i++)
if(a[1][i]>a[2][i])
swap(a[1][i],a[2][i]);
}
int ans=0;
for(int i=1;i<=k;i++){
int mn=3e4+10,mx=0;
for(int j=1;j<=n;j++)
mn=min(mn,a[i][j]),mx=max(mx,a[i][j]);
ans=max(ans,mx-mn);
}
write(ans),putc('\n');
}
flush();
}
}
$k=3$
让最大值最小,可以先考虑二分答案,检验答案 $x$ 是否可行。
考虑沿用上面的想法,先固定全局最小值在第一行,枚举全局最大值在第几行。
对于第 $i$ 列,我们枚举转了几次,判断是否满足最小值和最大值所在两行的限制(即在那一行上的数和那一行的最小值/最大值相差超不超过 $x$),如果满足限制,我们设剩下一行上的数为 $a$。
假设有一条数轴,我们可以在 $a$ 位置放上一个颜色为 $i$ 的点(每个颜色的点显然只有 $O(k)$ 个),我们要判断剩下一行的极差是否可以不超过 $x$,就是问是否有一条长度为 $x$ 的线段覆盖了所有 $n$ 种颜色。
我们维护 $[mn,mx]$ 内每个点作为左端点时,可以覆盖几种颜色,对于每种颜色,对于左端点 $l$,如果 $[l,l+x]$ 内有这个颜色的点就可以让这个左端点加一,我们考虑对每个这个颜色的点 $a$ 将 $[a-x,a]$ 内的左端点加一。
注意到不能重复加,对于每个颜色,我们开个 set 存这个颜色已经加入了的点,设要加入 $a$,$a$ 在集合内的前驱后继分别为 $p,q$,则需要将 $[\max(p+1,a-x),\min(a,q-x-1)]$ 加一。注意左端点大于右端点的时候。
差分即可,判断最大值是否等于 $n$,注意到不能给单次复杂度带上 $v$,需要维护有哪些位置被加了。
用并查集加上标记清空可做到时间复杂度为 $O(\sum nk^2\log v)$。
实现的代码由于排序有二 $\log$。
namespace Sub3{
const int N=5e4+10,M=3e4+10;
int n,a[3][N],mn,mx;
struct Delta{
struct File{
int p,v;
inline friend bool operator<(const File &a,const File &b){
return a.p<b.p;
}
}f[M];
int f[M],top,nxt[],g[M];
inline void clear(){
for(int i=1;i<=top;i++)
g[f[i].p]=0;
top=0;
}
inline void add(int p,int v){
if(!g[p]) g[p]=++top,f[top].p=p,f[top].v=0;
f[g[p]].v+=v;
}
inline void modify(int l,int r,int v){
if(l>r) return ;
add(r+1,-v),add(l,v);
}
inline int query(){
int mx=0;
sort(f+1,f+top+1);
for(int i=1,s=0;i<=top;i++){
s+=f[i].v;
mx=max(mx,s);
}
return mx;
}
}tr;
struct node{
int c,x;
};
multiset<int>ip[N];
vector<node>p;
inline int pre(int col,int x){
auto tmp=ip[col].upper_bound(x);
if(tmp==ip[col].begin()) return 0;
return *(--tmp);
}
inline int nxt(int col,int x){
auto tmp=ip[col].upper_bound(x);
if(tmp==ip[col].end()) return 3e5;
return *tmp;
}
inline void add(int col,int x,int mid,int v){
int pr=pre(col,x),nx=nxt(col,x);
if(pr==x) return ;
tr.modify(max({pr+1,x-mid,mn}),min(x,max(0,nx-mid-1)),v);
ip[col].insert(x);
}
inline void cl(int id){
if(id==0){
vector<node>op;
swap(p,op);
}else{
multiset<int>op;
swap(ip[id],op);
}
}
inline bool check(int mid){
cl(0);
tr.clear();
for(int i=1;i<=n;i++){
cl(i);
for(int j=0;j<=2;j++){
int x=a[(0+j)%3][i],y=a[(1+j)%3][i],
z=a[(2+j)%3][i];
if(x-mn<=mid&&mx-y<=mid)
p.push_back((node){i,z});
}
}
for(auto tmp:p){
int col=tmp.c,x=tmp.x;
add(col,x,mid,1);
}
int tmp=tr.query();
if(tmp==n) return 1;
cl(0);
tr.clear();
for(int i=1;i<=n;i++){
cl(i);
for(int j=0;j<=2;j++){
int x=a[(0+j)%3][i],y=a[(1+j)%3][i],
z=a[(2+j)%3][i];
if(mx-x<=mid&&y-mn<=mid)
p.push_back((node){i,z});
}
}
for(auto tmp:p){
int col=tmp.c,x=tmp.x;
add(col,x,mid,1);
}
tmp=tr.query();
return tmp==n;
}
inline void main(){
while(T--){
n=read();
mn=3e4,mx=0;
for(int i=0;i<k;i++)
for(int j=1;j<=n;j++)
a[i][j]=read(),
mn=min(a[i][j],mn),
mx=max(a[i][j],mx);
int l=0,r=mx-mn,ans=3e4;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
write(ans),putc('\n');
}
flush();
}
}
$k=4$
沿用上面的想法,设二分到 $x$,先固定全局最小值在第一行,枚举全局最大值在第几行。
对于第 $i$ 列,我们枚举转了几次,判断是否满足最小值和最大值所在两行的限制,如果满足限制,我们设剩下两行上的数为 $a,b$。我们在平面上 $(a,b)$ 位置放一个颜色为 $i$ 的点。判断是否有一个 $x\times x$ 的矩形能覆盖 $n$ 种颜色。
对于每一行维护有哪些点。考虑枚举矩形的下边界 $d$,上边界就是 $d-x$,考虑下移下边界,需要删掉 $d-x$ 行的所有点,加入 $d+1$ 行的所有点。删除方法和加入方法类似。
同样维护左边界 $x$ 坐标为 $l$ 的时候能覆盖几种颜色,需要动态修改,查询全局最大值,可以用线段树维护。
实现上,我们不能带上 $v$ 的复杂度,需要维护有哪些行有点,当前上下范围内有哪些行有点,可以用队列维护。注意清空只能清空有点的行,线段树也不能整棵树搜索清空,要一步步撤销操作。具体实现可以参考以下代码。
时间复杂度 $O(\sum nk^2\log^2v)$。
namespace Sub4{
const int N=1e4+10,M=3e4+10;
int n,a[4][N],mn,mx;
struct Segment_Tree{
#define ls (rt<<1)
#define rs (rt<<1|1)
int tag[M<<2],mx[M<<2];
inline void pushup(int rt){
mx[rt]=max(mx[ls],mx[rs]);
}
inline void pushadd(int rt,int v){
tag[rt]+=v,mx[rt]+=v;
}
inline void pushdown(int rt){
if(tag[rt]){
pushadd(ls,tag[rt]);
pushadd(rs,tag[rt]);
tag[rt]=0;
}
}
inline void modify(int rt,int l,int r,int L,int R,int v){
if(L>R) return ;
if(L<=l&&r<=R){
pushadd(rt,v);
return ;
}
pushdown(rt);
int mid=l+r>>1;
if(L<=mid) modify(ls,l,mid,L,R,v);
if(R>mid) modify(rs,mid+1,r,L,R,v);
pushup(rt);
}
inline int query(){
return mx[1];
}
}tr;
struct node{
int c,x;
};
multiset<int>ip[N];
vector<node>p[M];
inline int pre(int col,int x){
auto tmp=ip[col].upper_bound(x);
if(tmp==ip[col].begin()) return 0;
return *(--tmp);
}
inline int nxt(int col,int x){
auto tmp=ip[col].upper_bound(x);
if(tmp==ip[col].end()) return 3e5;
return *tmp;
}
struct DEL{
int l,r,v;
};
vector<DEL>del;
set<int>mdp,mbp;
inline void add(int col,int x,int mid,int v){
if(v==-1) ip[col].erase(ip[col].find(x));
int pr=pre(col,x),nx=nxt(col,x);
tr.modify(1,mn,mx,max({pr+1,x-mid,mn}),min(x,max(0,nx-mid-1)),v);
del.push_back((DEL){max({pr+1,x-mid,mn}),min(x,max(0,nx-mid-1)),-v});
if(v==1) ip[col].insert(x);
}
inline void cl(int id){
multiset<int>op;
swap(ip[id],op);
}
inline void CLEAR(){
for(auto tmp:del)
tr.modify(1,mn,mx,tmp.l,tmp.r,tmp.v);
vector<DEL>det;
swap(del,det);
for(auto tmp:mdp){
vector<node>mdq;
swap(p[tmp],mdq);
}
set<int>mdq;
swap(mdp,mdq);
set<int>mbq;
swap(mbp,mbq);
}
inline int beg(){
if(mbp.empty()) return 1e9;
return *mbp.begin();
}
inline void dbeg(){
mbp.erase(mbp.begin());
}
inline int nxt(int x){
auto tmp=mdp.upper_bound(x);
if(tmp==mdp.end()) return 3e5;
return *tmp;
}
inline bool check(int A,int B,int mid){
for(int i=1;i<=n;i++){
cl(i);
for(int j=0;j<=3;j++){
int x=a[(0+j)%4][i],y=a[(1+j)%4][i],
z=a[(2+j)%4][i],f=a[(3+j)%4][i];
if(A) swap(y,z);
if(B) swap(x,y);
if(x-mn<=mid&&mx-y<=mid)
p[z].push_back((node){i,f}),mdp.insert(z);
}
}
mbp=mdp;
for(int i=beg();i<=mid;i=nxt(i)){
for(auto tmp:p[i]){
int col=tmp.c,x=tmp.x;
add(col,x,mid,1);
}
}
int tmp=tr.query();
if(tmp==n) return CLEAR(),1;
for(int i=nxt(mid);i<=mx;i=nxt(i)){
for(auto tmp:p[i]){
int col=tmp.c,x=tmp.x;
add(col,x,mid,1);
}
for(int id=beg();id<=i-mid-1;id=beg()){
for(auto tmp:p[id]){
int col=tmp.c,x=tmp.x;
add(col,x,mid,-1);
}
dbeg();
}
int tmp=tr.query();
if(tmp==n) return CLEAR(),1;
}
return CLEAR(),0;
}
inline bool check(int mid){
int tmp=check(0,0,mid);
if(tmp) return 1;
tmp=check(0,1,mid);
if(tmp) return 1;
tmp=check(1,0,mid);
if(tmp) return 1;
return 0;
}
inline void main(){
while(T--){
n=read();
mn=3e4,mx=0;
for(int i=0;i<k;i++)
for(int j=1;j<=n;j++)
a[i][j]=read(),
mn=min(a[i][j],mn),
mx=max(a[i][j],mx);
int l=0,r=mx-mn,ans=3e4;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
write(ans),putc('\n');
}
flush();
}
}

浙公网安备 33010602011771号