2023.3.27 日寄
2023.03.27
模拟赛
\(~~~~\) 什么叫挂大分啊。
飞鸟和蝉
题意
\(~~~~\) 第 \(i\) 个点有权值,每个点可以跳 \([i-a_i,i+a_i]\) 内所有点。求序列上两个点之间互相到达的最小跳跃次数的最大值。
\(~~~~\) \(1\leq n\leq 2\times 10^5\).
题解
\(~~~~\) 考虑二分答案,即是否存在一对其互相到达次数都大于等于 \(d\)。
\(~~~~\) 这种题很显然的倍增套路,假设我们预处理出来过后那就可以计算出跳 \(d\) 次点 \(i\) 可以到达的区间 \(l_i,r_i\),于是就是求 \(u\in [l_v,r_v],v\in [l_u,r_u]\) 的点对 \((u,v)\) 是否存在。把所有 \(l\) 离线下来从小到大做,BIT 查询是否存在即可。
\(~~~~\) 在普通 ST 表上加一维表示跳 \(2^t\) 后 \(i\) 的左/右区间。
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
int n,L[19][19][300005],R[19][19][300005];
inline int QueryL(int x,int l,int r)
{
int t=__lg(r-l+1);
return min(L[x][t][l],L[x][t][r-(1<<t)+1]);
}
inline int QueryR(int x,int l,int r)
{
int t=__lg(r-l+1);
return max(R[x][t][l],R[x][t][r-(1<<t)+1]);
}
struct node{
int l,id;
node(){}
node(int LL,int ID){l=LL,id=ID;}
}P[300005];
inline bool cmp(node a,node b){return a.l<b.l;}
struct BIT{
int tr[300005];
inline int lowbit(int x){return x&(-x);}
void Clear(){for(int i=1;i<=n;i++) tr[i]=0;}
inline void Add(int x,int Val){for(;x<=n;x+=lowbit(x)) tr[x]+=Val;}
inline int Query(int x){int ret=0;for(;x;x-=lowbit(x)) ret+=tr[x];return ret;}
}BIT;
int posl[300005],posr[300005];
inline bool check(int mid)
{
BIT.Clear();
for(int i=1;i<=n;i++)
{
posl[i]=posr[i]=i;
for(int j=18;j>=0;j--)
{
if((mid>>j)&1)
{
int Tmpl=QueryL(j,posl[i],posr[i]),Tmpr=QueryR(j,posl[i],posr[i]);
posl[i]=Tmpl; posr[i]=Tmpr;
}
}
P[i]=node(posl[i],i);
}
int now=1;
sort(P+1,P+1+n,cmp);
for(int i=1;i<=n;i++)
{
while(now<=n&&P[now].l==i)
{
if(BIT.Query(P[now].id-1)) return true;
now++;
}
BIT.Add(posr[i],1);
}
return false;
}
int main() {
// freopen("fly.in","r",stdin);
// freopen("fly.out","w",stdout);
read(n);
for(int i=1,x;i<=n;i++) read(x),L[0][0][i]=max(1,i-x),R[0][0][i]=min(n,i+x);
for(int i=1,j=2;j<=n;i++,j<<=1)
{
for(int l=1;l+j-1<=n;l++)
{
L[0][i][l]=min(L[0][i-1][l],L[0][i-1][l+(j>>1)]);
R[0][i][l]=max(R[0][i-1][l],R[0][i-1][l+(j>>1)]);
}
}
for(int I=1;I<=18;I++)
{
for(int J=1;J<=n;J++)
{
L[I][0][J]=QueryL(I-1,L[I-1][0][J],R[I-1][0][J]);
R[I][0][J]=QueryR(I-1,L[I-1][0][J],R[I-1][0][J]);
}
for(int i=1,j=2;j<=n;i++,j<<=1)
{
for(int l=1;l+j-1<=n;l++)
{
L[I][i][l]=min(L[I][i-1][l],L[I][i-1][l+(j>>1)]);
R[I][i][l]=max(R[I][i-1][l],R[I][i-1][l+(j>>1)]);
}
}
}
int l=1,r=n,Ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) l=mid+1,Ans=mid;
else r=mid-1;
}
printf("%d",Ans+1);
return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/
智巧灵蕈大竞逐
\(~~~~\) 什么人出的题不用我多说了吧。
题意
\(~~~~\) 一个 \(n\times m\) 的矩阵,字符集大小为 \(62\)。三种操作:交换两个四联通的单元格,顺时针旋转一个 \(2\times 2\) 的子矩阵,将一个 \(n_i\times m_i\) 的子矩阵替换为预先给出的 \(k\) 个矩阵中的第 \(i\) 个矩阵。求构造最多 \(4\times 10^5\) 次操作,最多 \(400\) 次覆盖操作的方案使得其变为目标矩阵,或说明无解。
\(~~~~\) \(1\leq n,m,k\leq 20\).
题解
\(~~~~\) 考场想出来然后写不出来自闭。
\(~~~~\) 显然旋转操作可以被替换,所以只做交换。交换的时候也可以写一个函数实现矩阵内两个单独元素的交换,这样的操作次数大约是 \(\mathcal{O(2nm)}\) 的。
\(~~~~\) 接下来是替换的运用。考虑倒着做这个过程,考虑若一个矩阵是刚刚替换过来的,那目标矩阵里一定有一个子矩阵与它相同,那就替换这个子矩阵。而我们不关心在替换之前是啥,所以直接换成通配符即可。而为了凑这个子矩阵,我们可以把满足要求的都丢到左上角。替换完成后在之后的过程中通配符也可以去匹配子矩阵。
\(~~~~\) 最后只要满足当前反向处理过的目标矩阵的通配符足够表示出初始矩阵即可。
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
int n,m,K;
char rev[200];
int To[200],id;
char str[25];
int S[25][25],T[25][25];
struct oper{
int op,x,y;
oper(){}
oper(int Op,int X,int Y){op=Op,x=X,y=Y;}
};
struct Matrix{
int n,m;
int s[25][25];
}P[25];
vector <oper> Ans;
void Swap(int X1,int Y1,int X2,int Y2)
{
// cout<<X1<<" "<<Y1<<" "<<X2<<" "<<Y2<<endl;
if(T[X1][Y1]==T[X2][Y2]) return;
swap(T[X1][Y1],T[X2][Y2]);
int TmpX=X2,TmpY=Y2,lstx=0,lsty=0;
while(X2>X1) Ans.push_back(oper(-4,X2,Y2)),lstx=X2,lsty=Y2,X2--;
while(X2<X1) Ans.push_back(oper(-3,X2,Y2)),lstx=X2,lsty=Y2,X2++;
while(Y2>Y1) Ans.push_back(oper(-2,X2,Y2)),lstx=X2,lsty=Y2,Y2--;
while(Y2<Y1) Ans.push_back(oper(-1,X2,Y2)),lstx=X2,lsty=Y2,Y2++;
/*回去的路径必须先Y再X*/
while(lsty>TmpY) Ans.push_back(oper(-2,lstx,lsty)),lsty--;
while(lsty<TmpY) Ans.push_back(oper(-1,lstx,lsty)),lsty++;
while(lstx>TmpX) Ans.push_back(oper(-4,lstx,lsty)),lstx--;
while(lstx<TmpX) Ans.push_back(oper(-3,lstx,lsty)),lstx++;
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=m;j++) cerr<<rev[T[i][j]];
// cerr<<endl;
// }
// cerr<<"==="<<endl;
}
void Print()
{
printf("%d\n",(int)Ans.size());
for(int i=Ans.size()-1;i>=0;i--) printf("%d %d %d\n",Ans[i].op,Ans[i].x,Ans[i].y);
exit(0);
}
int cnt1[100],cnt2[100];
bool checkEnd()
{
for(int i=1;i<=id+1;i++) cnt1[i]=cnt2[i]=0;
int tot=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cnt2[S[i][j]]++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(T[i][j]>id) tot++;
else if(cnt2[T[i][j]]) cnt2[T[i][j]]--;
}
}
for(int i=1;i<=id;i++)
{
if(cnt2[i]>tot) return false;
tot-=cnt2[i];
}
return true;
}
bool checkMatrix(int x)
{
for(int i=1;i<=id+1;i++) cnt1[i]=cnt2[i]=0;
int tot=0,one=0;
for(int i=1;i<=P[x].n;i++) for(int j=1;j<=P[x].m;j++) cnt2[P[x].s[i][j]]++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(T[i][j]>id) tot++;
else if(cnt2[T[i][j]]) cnt2[T[i][j]]--,one++;
}
}
if(!one) return false;
for(int i=1;i<=id;i++)
{
if(cnt2[i]>tot) return false;
tot-=cnt2[i];
}
return true;
}
void Add(int N,int M,int &x,int &y){y++;if(y==M+1) x++,y=1;}
int main() {
// freopen("fungus.in","r",stdin);
// freopen("fungus.out","w",stdout);
read(n);read(m);read(K);
for(int i='0';i<='9';i++) To[i]=++id,rev[id]=i;
for(int i='a';i<='z';i++) To[i]=++id,rev[id]=i;
for(int i='A';i<='Z';i++) To[i]=++id,rev[id]=i;
rev[id+1]='*';
for(int i=1;i<=n;i++)
{
scanf("%s",str+1);
for(int j=1;j<=m;j++) S[i][j]=To[(int)str[j]];
}
for(int i=1;i<=n;i++)
{
scanf("%s",str+1);
for(int j=1;j<=m;j++) T[i][j]=To[(int)str[j]];
}
for(int i=1;i<=K;i++)
{
read(P[i].n);read(P[i].m);
for(int j=1;j<=P[i].n;j++)
{
scanf("%s",str+1);
for(int k=1;k<=P[i].m;k++) P[i].s[j][k]=To[(int)str[k]];
}
}
while(!checkEnd())
{
bool move=false;
for(int qwq=1;qwq<=K;qwq++)
{
if(!checkMatrix(qwq)) continue;
move=true;
for(int i=1;i<=id+1;i++) cnt1[i]=cnt2[i]=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cnt2[T[i][j]]++;
int nowx=1,nowy=1;
while(nowx<=P[qwq].n)
{
int Aim;
if(cnt2[P[qwq].s[nowx][nowy]]) Aim=P[qwq].s[nowx][nowy],cnt2[P[qwq].s[nowx][nowy]]--;
else Aim=id+1;
bool suc=false;
for(int j=1;j<nowx;j++)
{
for(int k=P[qwq].m+1;k<=m;k++)
{
if(T[j][k]==Aim)
{
suc=true;
Swap(j,k,nowx,nowy);
break;
}
}
if(suc) break;
}
if(!suc)
{
for(int j=nowy+1;j<=m;j++)
{
if(T[nowx][j]==Aim)
{suc=true;Swap(nowx,j,nowx,nowy);break;}
}
}
if(!suc)
{
for(int j=nowx+1;j<=n;j++)
{
for(int k=1;k<=m;k++)
{
if(T[j][k]==Aim)
{
suc=true;
Swap(j,k,nowx,nowy);
break;
}
}
if(suc) break;
}
}
Add(P[qwq].n,P[qwq].m,nowx,nowy);
}
Ans.push_back(oper(qwq,1,1));
for(int i=1;i<=P[qwq].n;i++) for(int j=1;j<=P[qwq].m;j++) T[i][j]=id+1;
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=m;j++) cerr<<rev[T[i][j]];
// cerr<<endl;
// }
// cerr<<"==="<<endl;
}
if(!move) return puts("-1")&0;
}
for(int i=1;i<=id;i++) cnt1[i]=cnt2[i]=0;
vector <int> V;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(T[i][j]<=id) cnt2[T[i][j]]++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int Aim;
if(cnt2[S[i][j]]) Aim=S[i][j],cnt2[S[i][j]]--;
else Aim=id+1;
bool suc=false;
for(int k=j+1;k<=m;k++)
{
if(T[i][k]==Aim)
{
Swap(i,k,i,j);
suc=true; break;
}
}
if(!suc)
{
for(int k=i+1;k<=n;k++)
{
for(int l=1;l<=m;l++)
{
if(T[k][l]==Aim)
{
Swap(k,l,i,j);
suc=true;break;
}
}
if(suc) break;
}
}
}
}
Print();
return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/
/*
那还是重构吧
3 3 1
OOO
GOG
BGB
OOO
GGG
BBB
3 1
B
G
B
*/
贪玩蓝月
题意
\(~~~~\) 一个双端队列,操作为:在首/尾加入二元组 \((w,v)\),删除首/尾元素,查询选择一些元素使得 \(\sum w\bmod p\in[l,r]\) 且 \(\sum v\) 最大。(\(p\) 提前给定。)
\(~~~~\) \(1\leq n\leq 5\times 10^4,p\leq 500\).
题解
\(~~~~\) 一个非常套路的双栈维护双端队列。两个栈,首加入/删除就在第一个栈操作,否则就在第二个栈操作。当某个栈删空了之后就把另一个栈的元素分一半过来。查询的时候枚举一个栈的 \(\sum w \bmod p\),然后计算出另一个栈的对应合法区间,ST表做即可。那么我们就只需要在栈里维护从底到顶的背包数组即可。
\(~~~~\) 由于分一半那就是减半警报器类似物,所以分一半的操作只会有 \(\log n\) 次,那最后复杂度在 \(\mathcal{O(np\log n)}\) 了。
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
char op[10];
struct node{
ll Ans[505];
node(){memset(Ans,0,sizeof(Ans));}
}Sta1[50005],Sta2[50005];
PII S1[50005],S2[50005],STmp[50005];
int p,Top1,Top2,Top;
void Add1(PII P)
{
int w,x;w=P.first; x=P.second;
Top1++; w%=p; S1[Top1]=mp(w,x);
for(int i=0;i<p;i++) Sta1[Top1].Ans[(i+w)%p]=max(Sta1[Top1-1].Ans[i]+x,Sta1[Top1-1].Ans[(i+w)%p]);
}
void Add2(PII P)
{
int w,x;w=P.first; x=P.second;
Top2++; w%=p; S2[Top2]=mp(w,x);
for(int i=0;i<p;i++) Sta2[Top2].Ans[(i+w)%p]=max(Sta2[Top2-1].Ans[i]+x,Sta2[Top2-1].Ans[(i+w)%p]);
}
int n,lg[505];
ll f[10][505];
void Init(node a)
{
for(int i=0;i<p;i++) f[0][i]=a.Ans[i];
for(int i=1;i<=9;i++)
for(int j=0;j+(1<<i)-1<p;j++) f[i][j]=max(f[i-1][j],f[i-1][j+(1<<(i-1))]);
}
ll Query(int l,int r)
{
int t=__lg(r-l+1);
return max(f[t][l],f[t][r-(1<<t)+1]);
}
int main() {
// freopen("tanwan3.in","r",stdin);
int ID;read(ID);lg[0]=-1;
for(int i=1;i<=500;i++) lg[i]=lg[i>>1]+1;
int w,x,l,r;read(n);read(p);
for(int i=1;i<p;i++) Sta1[Top1].Ans[i]=Sta2[Top2].Ans[i]=-1e18;
while(n--)
{
scanf("%s",op+1);
if(op[1]=='I'&&op[2]=='F') read(w),read(x),Add1(mp(w,x));
if(op[1]=='I'&&op[2]=='G') read(w),read(x),Add2(mp(w,x));
if(op[1]=='D'&&op[2]=='F')
{
if(!Top1) while(Top2) Add1(S2[Top2--]);
Top1--;
}
if(op[1]=='D'&&op[2]=='G')
{
if(!Top2) while(Top1) Add2(S1[Top1--]);
Top2--;
}
if(op[1]=='Q')
{
read(l);read(r);
if(!Top1&&!Top2)
{
if(l>0) puts("-1");
else puts("0");
continue;
}
node res=Sta1[Top1];
Init(Sta2[Top2]);
ll Ans=max(-1ll,Query(l,r));
for(int i=0;i<p;i++)
{
int L=(l-i+p)%p,R=(r-i+p)%p;
if(res.Ans[i]<=-1e9) continue;
else if(L<=R) Ans=max(Ans,Query(L,R)+res.Ans[i]);
else Ans=max(Ans,max(Query(0,R),Query(L,p-1))+res.Ans[i]);
}
printf("%lld\n",Ans);
}
if(!Top1&&Top2>1)
{
Top=0;
while(Top2>Top) STmp[++Top]=S2[Top2--];
while(Top2) Add1(S2[Top2--]);
while(Top) Add2(STmp[Top--]);
}
if(!Top2&&Top1>1)
{
Top=0;
while(Top1>Top) STmp[++Top]=S1[Top1--];
while(Top1) Add2(S1[Top1--]);
while(Top) Add1(STmp[Top--]);
}
}
return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
双栈背包是好东西啊
2
20 349
QU 297 305
IF 30976 29887
IG 22276 21153
DG
IF 29940 29024
QU 101 182
QU 68 226
IF 31798 1737
DF
DG
QU 0 5
QU 144 152
DG
QU 234 312
IG 8605 11859
QU 39 327
DF
IF 11919 23584
QU 157 329
IF 22792 13890
-1
-1
58911
0
-1
-1
11859
-1
*/