0112总结
四道题都比较套路,AK了。
T1 [模拟赛20230112] 密接
枚举区间的左端点,再枚举众数出现的次数,那么满足条件的右端点就是一段区间。令 \(pos1_i\) 为第一个出现 \(i\) 次的数的位置,\(pos2_i\) 位第二个。那么这段区间就是 \([pos1_i,min{pos2_i,pos1_i+1})\)。
然后考虑维护 \(pos\)。从右往左枚举左端点,在左端点新加进来一个数,考虑这个数在之后出现的位置,可以有可能将 \(pos\) 变小,就直接更新。
复杂度 \(O(100n)\)。
#include<bits/stdc++.h>
using namespace std;
inline int in(){
int x;
scanf("%d",&x);
return x;
}
const int N=1e5+5;
int n,m,a[N],b[N];
int mn1[105],mn2[105];
int id[N],pos[N][105],cnt[N];
int main(){
// freopen("close.in","r",stdin);
// freopen("close.out","w",stdout);
n=in();
for(int i=1;i<=n;i++)a[i]=b[i]=in();
sort(b+1,b+n+1),m=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+m+1,a[i])-b;
id[i]=++cnt[a[i]];
pos[a[i]][cnt[a[i]]]=i;
}
for(int i=1;i<=101;i++)mn1[i]=mn2[i]=n+1;
long long ans=0;
for(int i=n;i>=1;i--){
int p=id[i];
for(int x=p,y=1;x<=cnt[a[i]];x++,y++){
int v=pos[a[i]][x];
if(v<mn1[y])mn2[y]=mn1[y],mn1[y]=v;
else if(v<mn2[y])mn2[y]=v;
}
for(int j=1;j<=100;j++){
ans+=min(mn1[j+1],mn2[j])-mn1[j];
}
}
cout<<ans<<endl;
return 0;
}
T2 [模拟赛20230112] 确诊
没什么好说的,直接单调队列维护sg转移就行。(好像这个题也可以直接贪心)
#include<bits/stdc++.h>
using namespace std;
const int N=5e6+5;
int n,k;
int pri[N],inp[N],pc;
int f[N];
int q1[N],l1,r1,q2[N],l2,r2;
int q3[N],l3,r3,q4[N],l4,r4;
int main(){
// freopen("game.in","r",stdin);
// freopen("game.out","w",stdout);
cin>>n>>k;
for(int i=2;i<=n;i++){
if(!inp[i])pri[++pc]=i;
for(int j=1;j<=pc&&i*pri[j]<=n;j++){
inp[i*pri[j]]=1;
if(i%pri[j]==0)break;
}
}
l1=l2=l3=l4=1;
for(int i=2;i<n;i++){
while(l1<=r1&&i-k>q1[l1])l1++;
while(l2<=r2&&i-k>q2[l2])l2++;
while(l3<=r3&&i-k>q3[l3])l3++;
while(l4<=r4&&i-k>q4[l4])l4++;
if(inp[i]){
if(l2<=r2)f[i]=-f[q2[l2]]+1;
else if(l1<=r1)f[i]=-f[q1[l1]]-1;
if(f[i]>0){
while(l3<=r3&&f[q3[r3]]<=f[i])r3--;
q3[++r3]=i;
}else{
while(l4<=r4&&f[q4[r4]]<=f[i])r4--;
q4[++r4]=i;
}
}else{
if(l4<=r4)f[i]=-f[q4[l4]]+1;
else if(l3<=r3)f[i]=-f[q3[l3]]-1;
if(f[i]>0){
while(l1<=r1&&f[q1[r1]]<=f[i])r1--;
q1[++r1]=i;
}else{
while(l2<=r2&&f[q2[r2]]<=f[i])r2--;
q2[++r2]=i;
}
}
}
int ans=1e9;
for(int i=max(1,n-k);i<n;i++)if(!inp[i]){
if(ans==1e9)ans=f[i];
if(f[i]<=0&&ans>0)ans=f[i];
if(f[i]<=0||ans>0)ans=max(ans,f[i]);
}
if(ans==1e9)return cout<<0,0;
ans=-ans;if(ans>=0)ans++;else ans--;
cout<<ans<<endl;
return 0;
}
T3 [模拟赛20230112] 重症
比较套路的矩阵树板子题。
众所周知,矩阵树的边权不仅可以是一个数,还能是一个多项式。
但是我比较愚蠢,没有构造出多项式,不过这个东西是好用矩阵进行转移的,于是就突发奇想:能不能行列式套矩阵?
于是就试了试,具体地,要写矩阵加减乘以及求逆。但是就遇到了一个问题:矩阵乘法不满足交换律,那么求行列式时就不能进行行变换!
但是最后硬着头皮写完,居然AC了!
究其根本,虽然矩阵乘法没有交换律,但是这种矩阵树的题,矩阵对应的是每条边的边权,而对矩阵乘法的顺序进行交换,就相当于对树的dfs序的顺序进行交换,这显然是不会影响到最终的答案的,所以在矩阵树上套矩阵然后直接求行列式是正确的。
感觉矩阵树套矩阵可以解决很多问题,但是常数比直接套多项式大得多。
#include<bits/stdc++.h>
using namespace std;
inline int in(){
int x;
scanf("%d",&x);
return x;
}
const int N=205,mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){
int c=1;
for(;b;b>>=1,a=mul(a,a))if(b&1)c=mul(c,a);
return c;
}
struct mtx{
int a[4][4];
int* operator [](int x){return a[x];}
mtx(){
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
a[i][j]=0;
}
};
void print(mtx a){
for(int i=0;i<4;i++){
for(int j=0;j<4;j++)cout<<a[i][j]<<' ';
cout<<endl;
}
cout<<endl;
}
mtx I(){
mtx a;
for(int i=0;i<4;i++)a[i][i]=1;
return a;
}
mtx build(int c,int d){
mtx a=I();
a[0][1]=c,a[0][2]=d,a[0][3]=mul(c,d);
a[1][3]=d,a[2][3]=c;
return a;
}
mtx operator + (mtx a,mtx b){
mtx c;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
c[i][j]=add(a[i][j],b[i][j]);
return c;
}
mtx operator - (mtx a,mtx b){
mtx c;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
c[i][j]=add(a[i][j],mod-b[i][j]);
return c;
}
mtx operator * (mtx a,mtx b){
mtx c;
for(int i=0;i<4;i++)
for(int k=0;k<4;k++)
for(int j=0;j<4;j++)
c[i][j]=add(c[i][j],mul(a[i][k],b[k][j]));
return c;
}
mtx getinv(mtx a){
static int b[4][8];
for(int i=0;i<4;i++)
for(int j=0;j<8;j++){
if(j<4)b[i][j]=a[i][j];
else b[i][j]=(j==i+4);
}
for(int i=0;i<4;i++){
int inv=qpow(b[i][i],mod-2);
for(int j=i;j<8;j++)b[i][j]=mul(b[i][j],inv);
for(int j=i+1;j<4;j++){
for(int k=j+1;k<8;k++)
b[j][k]=add(b[j][k],mod-mul(b[i][k],b[j][i]));
}
}
for(int i=3;i>=0;i--){
for(int j=i-1;j>=0;j--){
int v=b[j][i];
for(int k=i;k<8;k++)
b[j][k]=add(b[j][k],mod-mul(v,b[i][k]));
}
}
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
a[i][j]=b[i][j+4];
return a;
}
int n,m;
mtx f[N][N];
mtx det(){
mtx res=I();
for(int i=1;i<n;i++){
res=res*f[i][i];
mtx inv=getinv(f[i][i]);
for(int j=i;j<n;j++)f[i][j]=f[i][j]*inv;
for(int j=i+1;j<n;j++){
for(int k=n-1;k>=i;k--)
f[j][k]=f[j][k]-(f[i][k]*f[j][i]);
}
}
return res;
}
int main(){
// freopen("icu.in","r",stdin);
// freopen("icu.out","w",stdout);
n=in(),m=in();
while(m--){
int x=in(),y=in(),c=in(),d=in();
mtx a=build(c,d);
f[x][x]=f[x][x]+a,f[y][y]=f[y][y]+a;
f[x][y]=f[x][y]-a,f[y][x]=f[y][x]-a;
}
mtx res=det();
cout<<res[0][3]<<endl;
return 0;
}
T4 [模拟赛20230112] 康复
比较套路的字符串题。
要字典序大于 \(X\),又要字典序最小,那么结果一定是 \(X\) 的一个前缀再加上一个字符。
于是枚举这个前缀 \(X_1 \sim X_i\),找出 \([l,r]\) 区间内这个前缀的出现位置,看一看有没有接下来一个字符大于 \(X_{i+1}\) 的,然后要接下来一个字符最小的。
具体地,就直接在 SAM 的 parent 树上对 26 个字符分别用线段树维护其末尾位置,然后查询的时候枚举字符再区间查询。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=1e6+5;
int n,q,ql[N],qr[N],qk[N];
char s[N],ss[N],*t[N];
int *pos[N];
int fa[M],len[M],ch[M][26],lst=1,tot=1,tag[M];
void insert(int c){
int p=lst,np=++tot;lst=np;
len[np]=len[p]+1;
for(;!ch[p][c];p=fa[p])ch[p][c]=np;
if(!p){fa[np]=1;return;}
int q=ch[p][c];
if(len[q]==len[p]+1){fa[np]=q;return;}
int nq=++tot;len[nq]=len[p]+1;
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(int i=0;i<26;i++)ch[nq][i]=ch[q][i];
for(;ch[p][c]==q;p=fa[p])ch[p][c]=nq;
}
vector<int> e[M];
int rt[M][26],tot1;
struct node{
int ls,rs;
}T[M*25];
#define ls(x) T[(x)].ls
#define rs(x) T[(x)].rs
void insert(int &p,int l,int r,int d){
if(!p)p=++tot1;
if(l==r)return;
int mid=l+r>>1;
if(d<=mid)insert(ls(p),l,mid,d);
else insert(rs(p),mid+1,r,d);
}
bool query(int p,int l,int r,int ql,int qr){
if(!p)return 0;
if(ql<=l&&r<=qr)return 1;
int mid=l+r>>1;bool res=0;
if(ql<=mid)res=query(ls(p),l,mid,ql,qr);
if(mid<qr&&!res)res=query(rs(p),mid+1,r,ql,qr);
return res;
}
int merge(int p,int q,int l,int r){
if(!p||!q)return p|q;
if(l==r)return p;
int mid=l+r>>1;
int x=++tot1;
ls(x)=merge(ls(p),ls(q),l,mid);
rs(x)=merge(rs(p),rs(q),mid+1,r);
return x;
}
void dfs(int x){
if(tag[x]||x==1){
int c=s[tag[x]+1]-'a';
insert(rt[x][c],0,n-1,tag[x]);
}
for(int y:e[x]){
dfs(y);
for(int i=0;i<26;i++)
rt[x][i]=merge(rt[x][i],rt[y][i],0,n-1);
}
}
int main(){
// freopen("heal.in","r",stdin);
// freopen("heal.out","w",stdout);
scanf("%s",s+1),n=strlen(s+1);
for(int i=1;i<n;i++){
insert(s[i]-'a');
while(len[fa[lst]]==len[lst])lst=fa[lst];
tag[lst]=i;
}
tag[1]=0;
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%d %d %s",ql+i,qr+i,ss+1);
qk[i]=strlen(ss+1);
t[i]=new char [qk[i]+3];
pos[i]=new int [qk[i]+2];
for(int j=0;j<qk[i]+2;j++)t[i][j]=ss[j];
lst=1;
for(int j=1;j<=qk[i];j++){
insert(ss[j]-'a');
while(len[fa[lst]]==len[lst])lst=fa[lst];
pos[i][j]=lst;
}
pos[i][0]=1;
}
for(int i=2;i<=tot;i++)e[fa[i]].push_back(i);
dfs(1);
for(int i=1;i<=q;i++){
int l=ql[i],r=qr[i],k=qk[i];
bool flag=0;
for(int j=min(r-l,k);j>=0;j--){
int c=j==k?0:t[i][j+1]-'a'+1,x=pos[i][j];
for(int y=c;y<26;y++){
if(query(rt[x][y],0,n-1,l+j-1,r-1)){
t[i][j+1]=y+'a',t[i][j+2]=0;
flag=1;break;
}
}
if(flag)break;
}
if(!flag)puts("-1");
else printf("%s\n",t[i]+1);
}
return 0;
}
[zyq互测] 交并补
比较有意思的数据结构题。
考虑最后进行的操作,肯定是先并后交(区间变长更易操作)。
讨论一下可以发现,最后只有两种可能:
1.直接由一些区间并出 \([x,y]\)
2.由一些区间并出 \([x,y']\),一些并出 \([x',y](x'<x,y'>y)\) 然后用这两个区间交出 \([x,y]\)。
如果只考虑第二种,是比较好做的:分别从左右倍增,找出最少需要多少区间可以覆盖 \([x,y]\)。具体地考虑倍增的过程:考虑每一次跳到哪一个区间,除了最后一次一定跳得尽量远,其它的操作都要跳到“潜力”最大的点(以当前节点为左端点,右端点最大的点,这样的点在下一步一定可以跳得最远)。于是最开始使用st表求出每个点下一步跳到哪,再直接倍增。
但是第一种情况不能每次贪心地跳得最远。那么就先从左边倍增,找出直接倍增跳出 \(y\) 之前的最后一个点 \(p\),这代表可以添加一条线段直接从 \(p\) 调到 \(y\) 右边。然后由于钦定了右端点,那么我们从右往左倍增,找出倍增跳出 \(p\) 之前的最后一个点 \(q\) 以及下一步的点 \(u\)。如果 \(u>x\) 那么这两段倍增的线段可以直接并出线段 \([x,y]\),而且显然是最少的,满足条件。如果 \(u<x\),那么左右分别在倍增的基础上新增一条边就可以有 \([x,y']\) 和 \([x',y]\),答案不超过倍增步数+2,但是如果有一条线段左端点在 \([x,p]\),右端点在 \([q,y]\) 那么就看可以把两边连起来,答案为倍增步数+1。判断有没有这样的线段可以使用主席树查找。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int BS=1<<20;
char buf[BS+5],*P1,*P2;
inline char GC(){
if(P1==P2)P1=buf,P2=buf+fread(buf,1,BS,stdin);
return P1==P2?EOF:*(P1++);
}
inline int in(){
int x=0,f=1;char c=GC();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=GC();}
while(c>='0'&&c<='9')x=x*10+c-48,c=GC();
return f>0?x:-1;
}
char buf1[BS+5],*P3=buf1;
inline void flush(){
fwrite(buf1,1,P3-buf1,stdout);
P3=buf1;
}
inline void PC(char c){
if(P3==buf1+BS)flush();
*(P3++)=c;
}
void out(int x){
static int st[10],tp;
if(x<0)PC('-'),x=-x;
if(x==0)PC('0');
while(x)st[++tp]=x%10,x/=10;
while(tp)PC(st[tp]+'0'),tp--;
PC('\n');
}
const int N=2e5+5;
int n,q,m;
int mark[N];
int lp[N],rp[N];
int rt[N],tot;
vector<int> v[N];
struct node{
int ls,rs,val;
}t[N*22];
#define ls(x) t[(x)].ls
#define rs(x) t[(x)].rs
#define val(x) t[(x)].val
void insert(int &p,int pre,int l,int r,int d){
p=++tot,t[p]=t[pre],val(p)++;
if(l==r)return;
int mid=l+r>>1;
if(d<=mid)insert(ls(p),ls(pre),l,mid,d);
else insert(rs(p),rs(pre),mid+1,r,d);
}
int find(int p,int q,int l,int r,int qr){
if(val(p)-val(q)==0||l>qr)return 0;
if(l==r)return l;
int mid=l+r>>1,res=find(rs(p),rs(q),mid+1,r,qr);
if(!res)res=find(ls(p),ls(q),l,mid,qr);
return res;
}
int op;
inline int Max(int a,int b){
if(!op)return rp[a]>rp[b]?a:b;
return lp[a]<lp[b]?a:b;
}
int st[18][N],lg[N];
int L[N][18],R[N][18];
inline int get(int l,int r){
int k=lg[r-l+1];
return Max(st[k][l],st[k][r-(1<<k)+1]);
}
void build_st(int OP){
op=OP;
for(int i=2;i<=m;i++)lg[i]=lg[i>>1]+1;
for(int i=1;i<=m;i++)st[0][i]=i;
for(int i=1;i<18;i++){
for(int j=1;j<=m-(1<<i)+1;j++){
st[i][j]=Max(st[i-1][j],st[i-1][j+(1<<i-1)]);
}
}
}
struct Res{
int step,now,nxt;
};
Res go_right(int l,int r){
if(rp[l]>=r)return {0,l,rp[l]};
int step=1,now=l,nxt;
for(int i=17;i>=0;i--){
if(rp[R[now][i]]<r)now=R[now][i],step+=1<<i;
}
nxt=rp[R[now][0]],now=rp[now];
return {step,now,nxt};
}
Res go_left(int l,int r){
if(lp[r]<=l)return {0,r,lp[r]};
int step=1,now=r,nxt;
for(int i=17;i>=0;i--){
if(lp[L[now][i]]>l)now=L[now][i],step+=1<<i;
}
nxt=lp[L[now][0]],now=lp[now];
return {step,now,nxt};
}
struct edge{
int l,r;
}e[N];
int main(){
n=in(),m=2e5;
for(int i=1;i<=m;i++)lp[i]=m+1;
m=0;
for(int i=1;i<=n;i++){
int l=in(),r=in();
e[i].l=l,e[i].r=r;
if(l==r)mark[l]=1;
v[l].push_back(r);
rp[l]=max(rp[l],r);
lp[r]=min(lp[r],l);
m=max(m,r);
}
sort(e+1,e+n+1,[](edge a,edge b){return a.l<b.l;});
for(int i=1;i<=m;i++){
rt[i]=rt[i-1];
for(int j:v[i])insert(rt[i],rt[i],1,m,j);
}
build_st(0);
for(int i=m;i>=1;i--){
if(!rp[i])continue;
R[i][0]=get(i,rp[i]);
for(int j=1;j<18;j++)R[i][j]=R[R[i][j-1]][j-1];
}
build_st(1);
for(int i=1;i<=m;i++){
if(lp[i]>m)continue;
L[i][0]=get(lp[i],i);
for(int j=1;j<18;j++)L[i][j]=L[L[i][j-1]][j-1];
}
q=in();
while(q--){
int l=in(),r=in();
if(!rp[l]||lp[r]>m){out(-1);continue;}
Res res1=go_right(l,r);
if(res1.nxt<r){out(-1);continue;}
if(res1.nxt==r){out(res1.step);continue;}
Res res2=go_left(res1.now,r);
if(res2.nxt>res1.now){out(-1);continue;}
if(res2.nxt>=l){out(res1.step+res2.step);continue;}
int ans=1;
int x=find(rt[res1.now],rt[l-1],1,m,r);
if(x>=res2.now)ans=0;else ans=1;
out(res1.step+res2.step+ans);
}
flush();
return 0;
}

浙公网安备 33010602011771号