『模拟赛』暑假集训CSP提高模拟15

Rank

小寄一手

image

A. 串串

原[THUPC2018] 绿绿和串串

一眼 manacher,但是当时虚空了没搞懂,只打了暴力(还挂分了

稍微学了一下,板子很短,主要依据是可以通过一个已经确定的与目前最长回文串的中心对称的半径来预先确定目标点最短的回文半径长度,从而优化复杂度达到线性。

manacher 主要利用了回文串的一大性质:在一个回文串内任意一段区间,一定存在一个关于回文中心对称的区间,二者全等;若该区间是回文串,其对称区间也是回文串,二者回文半径相等。

根据这个性质,我们便可以在针对不同回文中心的求最大回文半径时预处理得到一个下界,由下界不断扩展得到真实的(最长)回文半径。手模一下很好理解,因为对称区间的半径到达极限,而目标区间的左侧与右侧还未进行查询,所以对称区间的回文半径是下界。按这样操作,最后复杂度为线性,在数据范围大的情况下优势尤为明显。

Manacher 模板

输入一个串,找到子串中最大回文串长度。

const int Ratio=0;
namespace Manachar
{
	string s;
	char ch[N];
	int len[N],cnt;
	short main()
	{
		cin>>s;
		int lenn=s.size(),ans=1;
		ch[0]='~',ch[1]='#';// 防越界
		for(int i=0;i<lenn;i++) ch[2*i+2]=s[i],ch[2*i+3]='#';
		cnt=2*lenn+1;
		ch[cnt+1]='%';
		int maxx=0,p=0;
		for(int i=1;i<=cnt;i++)
		{
			if(i<=maxx) len[i]=min(maxx-i+1,len[2*p]-i);
			while(ch[i-len[i]]==ch[i+len[i]]) len[i]++;
			if(len[i]+i>maxx) maxx=len[i]+i-1,p=i;
			if(len[i]>ans) ans=len[i];
		}
		printf("%d\n",ans-1);
		return Ratio;
	}
}
点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx int
inline lx qr()
{
	char ch=getchar();lx x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#undef lx
#define qr qr()
const int Ratio=0;
const int N=3e7+5;
const int mod=10007;
string s;
char ch[N];
int len[N],cnt;
bool yz[N];
namespace Wisadel
{
    short main()
    {
        // freopen(".in","r",stdin),freopen(".out","w",stdout);
        int T=qr;
        while(T--)
        {
            cin>>s;int lenn=s.size();
            ch[0]='~';ch[1]='#';
            fo(i,0,lenn-1) 
                ch[2*i+2]=s[i],ch[2*i+3]='#';
            cnt=2*lenn+1;
            ch[cnt+1]='%';
            int maxx=0,p=0;
            fo(i,1,cnt)
            {
                len[i]=0,yz[i]=0;
                if(i<=maxx) len[i]=min(maxx-i+1,len[2*p-i]);
                while(ch[i-len[i]]==ch[i+len[i]]) len[i]++;
                if(i+len[i]>maxx) maxx=i+len[i]-1,p=i;
            }
            fu(i,cnt,1)
                if(i+len[i]-1==cnt) yz[i]=1;
                else if(yz[i+len[i]-2]&&i==len[i]) yz[i]=1;
            fo(i,1,cnt) if(ch[i]>='a'&&ch[i]<='z'&&yz[i]) printf("%d ",i/2);
            printf("\n");
        }
        return Ratio;
    }
}
int main(){return Wisadel::main();}

B. 排排

躺尸题,结论找到了,但由于存在一点细节问题所以多测没清空挂 5pts。

听到没给大样例就能猜到答案应该就几个数而且很小。于是开始找结论:

  1. 当原排列本身单调递增时,答案显然为 0;
  2. 考虑答案为 1 的情况,在该排列中存在某一个位置上的数将序列左右划分的情况恰好与在升序排列中的情况相同,现上可以通过记录到每一位时的最大值来实现;
  3. 更大的答案我们通过逆推实现,我们取 1 在开头且不为结论 1 的情况,易得只需要取 \(k=1\) 一次操作即可达到要求,同理 n 在末尾亦如此,那么 1 不在开头且 n 不在末尾的情况答案显然为 2;
  4. 有没有更大的答案?考虑在结论 3 中找特殊情况,答案为 2 的前提是一次操作可以将 1 移到开头或将 n 移到末尾,考虑一次操作无法达到如上要求的情况,即无论如何取 \(k\) 的值都无法将 1 置于 \(k\) 左侧且无法将 n 置于 \(k\) 右侧,容易想到是 1 在末尾且 n 在开头的情况。

得出所有结论,问题就迎刃而解了。

点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx int
inline lx qr()
{
	char ch=getchar();lx x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#undef lx
#define qr qr()
const int Ratio=0;
const int N=3e5+5;
const int mod=10007;
int n;
int a[N];
priority_queue<int,vector<int>,less<int> >q;
namespace Wisadel
{
    short main()
    {
        int T=qr;
        while(T--)
        {
            n=qr;bool ok0=1,ok1=0;
            memset(a,0,sizeof a);
            while(q.size()) q.pop();
            fo(i,1,n)
            {
                a[i]=qr;
                if(a[i]!=i) ok0=0;
            }
            if(ok0){printf("0\n");continue;}
            if(a[1]==1||a[n]==n){printf("1\n");continue;}
            fo(i,1,n)
            {
                q.push(a[i]);
                if(q.top()==i&&a[i+1]==i+1){ok1=1;break;}
				// 笨重的方法不推荐使用,而且不加 memset 会 WA
            }
            if(ok1){printf("1\n");continue;}
            if(a[1]==n&&a[n]==1)printf("3\n");
            else printf("2\n");
        }
        return Ratio;
    }
}
int main(){return Wisadel::main();}

C. 序序

有点不可做的感觉。

赛时拿到了 10pts 的暴力并且推柿子拿到了特殊性质的 10pts,还可以。

先说说保证 \(s_i\le s_{i+1}\) 的做法。

已知序列单调不下降,那么最大值最小值乘积也显然为最左边的数乘上最右边的数,得到原始柿子:

\[ans=\sum_{i=L}^R \sum_{j=i}^R a_i\times a_j \]

求个前缀和,开推:

\[\begin{aligned} ans &= \sum_{i=L}^R \sum_{j=i}^R a_i\times a_j \\ &= \sum_{i=L}^R a_i\times\left(sum_R-sum_{i-1}\right) \\ &= a_L\times\left(sum_R-sum_{L-1}\right)+\ldots +a_R\times \left(sum_R-sum_{R-1}\right) \\ &= sum_R\times\left(sum_R-sum_{L-1}\right) - \sum_{i=L}^R a_i\times sum_{i-1} \end{aligned} \]

根据数据范围,我们知道但凡带 \(\sum\) 的都会使复杂度爆炸,因此我们每一步化简都要尽量消去它。

到这一步,我们发现如果再记录一个 \(a_i\times sum_{i-1}\) 这样的前缀和即可将最后一个 \(\sum\) 消去,最终结果为:

\[ans=sum_R\times\left(sum_R-sum_{L-1}\right)-\left(jsum_R-jsum_{L-1}\right) \]

点击查看代码
// 预处理
for(int i=1;i<=n;i++)
{
	cin>>a[i];
	sum[i]=(sum[i-1]+a[i])%mod;
	jsum[i]=(jsum[i-1]+a[i]*sum[i-1]%mod+mod)%mod;
}
// 查询
while(m--)
{
	int l,r;ll ans=0;
	cin>>l>>r;
	ans=(sum[r]-sum[l-1]+mod)%mod*sum[r]%mod;
	ans=(ans-(jsum[r]-jsum[l-1]+mod)%mod+mod)%mod;
	printf("%lld\n",ans);
}

D. 桥桥

原[APIO2019] 桥梁

暴力寄了挂 29pts。

虽然这次打的不太好,但感觉收获是很多的,学了 Manacher,吃了个多测的教训,因为大数组 memset 而 TLE,还推出来一个柿子。

所以高强度模拟赛就像题海,虽然这个受状态影响更大,但一样能收获很多小知识和经验教训,实力实在隐藏中提升的。

积少成多,加油。


完结撒花~

脑补一张飞霄老婆 wink 的图片。

posted @ 2024-08-07 20:09  DrRatio  阅读(43)  评论(0编辑  收藏  举报