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;
}
posted @ 2023-01-12 19:27  蒟蒻_william555  阅读(65)  评论(0)    收藏  举报