首页 > 其他分享 >2024.8.7 模拟赛 15

2024.8.7 模拟赛 15

时间:2024-09-05 10:50:40浏览次数:9  
标签:15 2024.8 res int vs 端点 id 模拟 回文

模拟赛

。。。

T1 绿绿和串串

学习 manacher。

先说求回文串,manacher 算法,每次记录向右能延伸最长的回文串和回文中心。

这样对于新扩展的字符,按已有的回文中心对称过去,会得到一个已经求出的回文长度,在这个基础上向两端扩展就好了。

对于普通的回文串,有奇回文和偶回文两种,为了方便计算,我们通常在每两个字符之间插入无用字符,这样就可以都按奇回文来算了,注意长度也要有变化。

回到这道题,很容易发现我们要找出前缀回文串或者后缀回文串(前缀时要判一下右端点)的回文中心,并且都是奇回文,所以直接上 马拉车。

为了方便,代码实现中反转了字符串,注意最后输出答案要翻回来。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 5e6+5;
int t,n;
char s[N];
int vs[N];
int p[N];
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d",&t);
	while(t--)
	{
		scanf("%s",s+1); n=strlen(s+1);
		if(n==1) {printf("1\n"); continue;}
		for(int i=0;i<=n+1;i++) vs[i]=p[i]=0;
		s[0]='&'; s[n+1]='*';
		reverse(s+1,s+1+n); p[1]=1;
		for(int i=1,r=0,mid=0;i<=n;i++)
		{
			if(i<=r) p[i]=min(p[2*mid-i],r-i+1);
			while(s[i+p[i]]==s[i-p[i]]) p[i]++;
			if(i+p[i]-1>r) mid=i,r=i+p[i]-1;
			if(i-p[i]+1==1) vs[i]=1;
			else if(i+p[i]-1==n&&vs[i-p[i]+1]) vs[i]=1;
		}
		for(int i=1;i<=n;i++) if(vs[n-i+1]) printf("%d ",i);
		putchar('\n');
	}
	return 0;
}

T2

思维题(发现没有大样例)。

首先特判不用动的情况。

只需要考虑收尾两个位置,假如第一个数不是最大值,那么动一下它,后面的都会排好,这时候再动一下最后一个(一定已经是最大值了)就好了。

也可以选择动最后一个数,前提是第一个数不是最大值或最后一个数不是最小值。这时候只需要两次。

如果恰好第一个数就是最大值并且最后一个数就是最小值呢?这时候任意动一次中间的数就和上述情况一样了。需要 \(3\) 次。

另外,还有可能就是恰好有一个位置能一次解决,特判一下就好。(赛事唐氏 BIT,其实可以直接记录最大值)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int t;
int n,a[N];

void work()
{
	int mx=0;
	bool fl=1;
	for(int i=1;i<=n;i++) fl&=(a[i]==i);
	if(fl) return printf("0\n"),void(0);
	for(int i=1;i<=n;i++)
	{
		mx=max(mx,a[i]);
		if(a[i]==i&&mx==i) return printf("1\n"),void(0);
	}
	if(a[1]!=n||a[n]!=1) return printf("2\n"),void(0);
	return printf("3\n"),void(0);
}

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		work();
	}
	return 0;
}

T3 一个古老的序列

链接就是题解,讲的很清晰

学习新知识,线段树维护历史版本和!!!

我们要求询问区间所有子区间的 \(min \times max\),当然考虑每一个数作为最大值或最小值的管辖范围。

先考虑只求一个区间的 \(min \times max\),可以直接单调栈维护:枚举右端点,确定每一个点作为最大值或最小值的管辖范围,然后线段树维护对于每一个左端点,当前右端点对应答案

但是我们发现上述想法只能求出一个区间的答案,而不是所有子区间的答案和。

如果想同时处理所有子区间,其实就是对于每一个左端点,要维护左端点到右端点每一个时刻的答案和。这都是我们之前计算过的,所以考虑怎么维护历史版本和

有一个很巧妙的引入是矩阵乘法!。用一维存当前值,另一维存历史版本和。

我们设计一个转移矩阵来将当前答案统计到历史和里:

\[\begin{bmatrix} a & b \end{bmatrix} \times \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} a & a+b \end{bmatrix} \]

这样写相当方便啊,每次更新完乘一次转移矩阵就好了,其他操作完全没有变化。

可能会比不用矩阵直接转移慢一点,但是写法更简单。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5,mod = 1e9+7;
int n,m,a[N],ans[N];
struct Q {int l,id;}; vector<Q> q[N];
int qx[N<<1],qi[N<<1],lx,li;
struct M
{
	int a[2][2];
	bool jd()
	{
		return a[0][0]==1&&a[1][1]==1&&a[0][1]==0&&a[1][0]==0;
	}
	M () {memset(a,0,sizeof(a));}
	void rs() {a[0][1]=a[1][0]=0; a[0][0]=a[1][1]=1;}
	M operator * (const M &x) const 
	{
		M c;
		for(int i=0;i<2;i++)
			for(int k=0;k<2;k++)
				for(int j=0;j<2;j++)
					c.a[i][j]=(c.a[i][j]+1ll*a[i][k]*x.a[k][j])%mod;
		return c;
	}
};
int qpow(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1) res=1ll*res*a%mod;
		a=1ll*a*a%mod; b>>=1;
	}
	return res;
}
M con(int c)
{
	M res; res.a[0][0]=c; res.a[1][1]=1;
	return res;
}

namespace SEG
{
	struct T {int l,r; M sum,lz;} tr[N<<2];
	inline void pushup(int k)
	{
		tr[k].sum.a[0][0]=(1ll*tr[k<<1].sum.a[0][0]+tr[k<<1|1].sum.a[0][0])%mod;
		tr[k].sum.a[0][1]=(1ll*tr[k<<1].sum.a[0][1]+tr[k<<1|1].sum.a[0][1])%mod;
	}
	void bui(int k,int l,int r)
	{
		tr[k].lz.rs(); tr[k].sum.a[0][0]=r-l+1;
		tr[k].l=l; tr[k].r=r;
		if(l==r) return;
		int mid=l+r>>1;
		bui(k<<1,l,mid); bui(k<<1|1,mid+1,r);
	}
	void down(int k)
	{
		if(tr[k].lz.jd()) return;
		tr[k<<1].sum=tr[k<<1].sum*tr[k].lz;
		tr[k<<1].lz=tr[k<<1].lz*tr[k].lz;
		tr[k<<1|1].sum=tr[k<<1|1].sum*tr[k].lz;
		tr[k<<1|1].lz=tr[k<<1|1].lz*tr[k].lz;
		tr[k].lz.rs();
	}
	void add(int k,int l,int r,M c)
	{
		if(l<=tr[k].l&&tr[k].r<=r)
		{
			tr[k].sum=tr[k].sum*c; tr[k].lz=tr[k].lz*c; return;
		}
		down(k);
		int mid=tr[k].l+tr[k].r>>1;
		if(l<=mid) add(k<<1,l,r,c);
		if(r>mid) add(k<<1|1,l,r,c);
		pushup(k);
	}
	int que(int k,int l,int r)
	{
		if(tr[k].l>=l&&tr[k].r<=r) return tr[k].sum.a[0][1];
		down(k);
		int mid=tr[k].l+tr[k].r>>1,res=0;
		if(l<=mid) res=que(k<<1,l,r);
		if(r>mid) res=(res+1ll*que(k<<1|1,l,r))%mod;
		return res;
	}
}; using namespace SEG;

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	bui(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int l,r; scanf("%d%d",&l,&r);
		q[r].push_back({l,i});
	}
	lx=li=0; M I; I.rs(); I.a[0][1]=1;
	for(int i=1;i<=n;i++)
	{
		while(lx&&a[qx[lx]]<a[i])
		{
			int c=qpow(a[qx[lx]],mod-2);
			add(1,qx[lx-1]+1,qx[lx],con(c));
			lx--;
		}
		while(li&&a[qi[li]]>a[i])
		{
			int c=qpow(a[qi[li]],mod-2);
			add(1,qi[li-1]+1,qi[li],con(c));
			li--;
		}
		add(1,qx[lx]+1,i,con(a[i]));
		add(1,qi[li]+1,i,con(a[i]));
		qx[++lx]=i; qi[++li]=i;
		add(1,1,i,I);
		for(auto j:q[i]) ans[j.id]=que(1,j.l,i)%mod;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}

T4 桥梁

先考虑暴力做法,暴力修改后遍历所有边,找出 满足条件的边,并查集维护连通性,然后查询。

考虑操作分块(怎么想到的?)。

设定块长为 \(S\),那么我们将每 \(S\) 次操作划分为一块,这样块内的查询和修改操作都是小于等于 \(S\) 的,由此对于块内我们可以考虑一些暴力的操作。

仅看一个块内,我们把所有边按从大到小排序,查询也按从大到小排序,这样对于没被修改的边,加入的时候按查询的顺序加就好了,类似单调性?先加的边一定能满足后面的查询。

考虑会被修改的边,因为一个块内最多有 \(S\) 次修改操作,所以暴力将时间小于询问并且乘重大于询问加入连通块,用可撤销并查集维护,每次暴力修改 。

块长 \(\sqrt{n}\) 时复杂度 \(O(n\sqrt{n}\log(n))\)。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5,S = 1024;
int n,m,qq;
struct E {int u,v,w,id;} e[N];
struct Q {int id,bc,t;};
inline bool cmpe(E &x,E &y) {return x.w>y.w;}
inline bool cmpq(Q &x,Q &y) {return x.bc>y.bc;}
vector<Q> q,f;
int ys[N],vs[N],top,fa[N],sz[N],st[N],ans[N];
inline int find(int x) {return x==fa[x]?x:find(fa[x]);}

inline void merge(int u,int v)
{
	u=find(u); v=find(v);
	if(u==v) return;
	if(sz[u]<sz[v]) swap(u,v);
	st[++top]=v;
	sz[u]+=sz[v],fa[v]=u;
}
inline void back(int lst)
{
	while(top>lst)
	{
		int v=st[top--];
		sz[fa[v]]-=sz[v];
		fa[v]=v;
	}
}
void work()
{
	sort(e+1,e+1+m,cmpe);
	sort(q.begin(),q.end(),cmpq);
	for(int i=1;i<=m;i++) ys[e[i].id]=i;
	vector<Q> tmp;
	for(Q i:f) vs[i.id]=-1,tmp.push_back({i.id,e[ys[i.id]].w,0});
	for(Q i:f) tmp.push_back(i);
	for(int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
	top=0;
	for(int i=0,t=1;i<q.size();i++)
	{
		while(t<=m&&e[t].w>=q[i].bc)
		{
			if(!vs[e[t].id]) merge(e[t].u,e[t].v);
			t++;
		}
		int lst=top;
		for(Q x:tmp)
			if(x.t<=q[i].t) vs[x.id]=x.bc;
		for(Q x:f)
			if(vs[x.id]>=q[i].bc) merge(e[ys[x.id]].u,e[ys[x.id]].v);
		ans[q[i].t]=sz[find(q[i].id)];
		back(lst);
	}
	for(Q i:f) e[ys[i.id]].w=i.bc,vs[i.id]=0;
	f.clear(); q.clear();
}
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		e[i]={x,y,z,i};
	}
	scanf("%d",&qq);
	for(int i=1;i<=qq;i++)
	{
		int c,x,y; scanf("%d%d%d",&c,&x,&y);
		c==1?f.push_back(Q{x,y,i}):q.push_back(Q{x,y,i});
		if(i%S==0) work();
	}
	if(qq%S) work();
	for(int i=1;i<=qq;i++) if(ans[i]) printf("%d\n",ans[i]);
	return 0;
}

标签:15,2024.8,res,int,vs,端点,id,模拟,回文
From: https://www.cnblogs.com/ppllxx-9G/p/18372562

相关文章

  • 2024.8.8模拟赛16
    模拟赛重拾题解(刚刚写过一版忘保存了)T1其实就是个最长公共子序列的变形。把一样的数才匹配换成有倍数关系就匹配。最长公共子序列:一般转化为最长上升子序列,即在一个串中的数\(a\),找到它在另一个串中的位置\(j\),从\(1\dotsj-1\)转移即可,取最大值可用树状数组维护前缀最......
  • Day85 APP攻防-资产收集篇&反证书检验&XP框架&反代理VPN&数据转发&反模拟器
    知识点1、APP资产-抓包突破&反模拟器2、APP资产-抓包突破&反证书检验3、APP资产-抓包突破&反代理VPN没有限制过滤的抓包问题:1、抓不到-工具证书没配置好2、抓不到-app走的不是http/s不走http/s协议的数据包抓不到可以用封包抓有限制过滤的抓包问题:3、抓不到-......
  • 6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)
    目录一.堆(Heap)的基本介绍二.堆的常用操作(以小根堆为例)三.实现代码3.1堆结构定义3.2向下调整算法*3.3初始化堆*3.4销毁堆3.4向上调整算法* 3.5插入数据3.6删除数据3.7返回堆顶数据四.下篇内容1.堆排序2.TopK问题一.堆(Heap)的基本介绍    ......
  • 第15篇 线程锁的问题
    在C#中,加锁是一种常见的多线程编程技术,它用于保护共享资源,防止多个线程同时对共享资源进行访问,导致数据错乱或者异常。会有以下几种情况需要用到线程锁。1.多线程访问共享资源如果多个线程需要访问同一个共享资源(例如全局变量、静态变量等),那么需要在访问该资源时进行加锁。否则,......
  • 免费视频压缩软件下载?2024年最新15款好用的视频压缩工具推荐!
    做过短视频的朋友,肯定都知道大部分平台的视频大小都有限制,那么如何通过无损压缩,上传体积小、清晰度高的视频,成为了不少同学的锥心之痛!今天,俺就以一名剪辑师的身份,分享圈里人用的比较多的视频压缩工具,大部分都是免费哒,绝对可以让你的是制作如虎添翼!1、压缩宝官网:https://www.......
  • 深入了解链表 list 之的模拟实现 list (C++)
    1.基本框架关于链表我们知道其是一个双向循环结构,并且由许多节点组成,各个节点之间内存空间不一定连续,每个节点均有前驱指针与后继指针,下面我们使用类模版来实现一个适用于存储大部分数据类型的链表,由下面代码我们可以看到一些基础框架与很简单的函数size返回长度与empty判断......
  • Charles - 夜神模拟器证书安装App抓包-charles监控手机出现unknown 已解决
    1.Openssl安装http://slproweb.com/products/Win32OpenSSL.htmlexe下载安装后进行配置 新建系统变量OPENSSL_HOME,变量值设为(绝对路径)软件安装目录下的bin直接浏览 编辑用户变量path,新建%OPENSSL_HOME%,最后点击确定 查看openssl版本,输入命令:opensslversion  2......
  • 2024.8
    1.ARC183DKeepPerfectlyMatched思考了一会后,发现答案是存在一个上界的:以重心\(r\)定根,一条边至多经过\(sz_i\)次。而这个东西一出来,就知道一定是能顶到的了,因为太典了。我们考虑,当且仅当,每次删除的两个叶子,都属于\(r\)的两颗不同子树,符合要求。而一次删除,怎么样才能让......
  • POJ - 3150
    题解题意:题面很臭很长。大意是,有一个大小为N的环,给出M,K,D,以及N个数。我们进行K次操作,每次操作把距离当前点不超过D的累加到当前点,结果模M。思路:因为要进行K次,每次的原则是一样的,我们可以想到用矩阵来优化,如果i能到达j,把么base[i][j]=1;则结果ans=A(base^K)。但是需要优化,时间复杂......
  • 洛谷 P2515 软件安装
    洛谷P2515软件安装题意现在我们的手头有\(N\)个软件,对于一个软件\(i\),它要占用\(W_i\)的磁盘空间,它的价值为\(V_i\)。我们希望从中选择一些软件安装到一台磁盘容量为\(M\)计算机上,使得这些软件的价值尽可能大(即\(V_i\)的和最大)。但是现在有个问题:软件之间存在依赖......