CSP2024 前集训:多校A层冲刺NOIP2024模拟赛04

前言

image

T1 签了。

T2 一眼后缀数组板子,但是复杂度是 \(O(nq\log(n))\) 的,极限数据本地 \(4\) 秒,但如果您会 \(O(n)\) 求后缀数组的话就直接过掉了,但赛时数据貌似纯随机,遂可以直接过掉,可以优化成 \(O(n^2\log(n)+nq)\)\(O(n^2\log(n)+q)\) 的,赛时想打这个但是怕常熟大和上面区别不大,遂没打,实际上第二种快得多,极限数据本地 \(500ms\),正常评测姬都能过。

T3 想到建两棵 trie 树但是细节没想出来。

T4 想到对模数讨论,但不会 crt 遂根本没往那边想。

T1 02表示法

高精度加二进制分解板子,递归即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=610;
template<typename Tp> inline void read(Tp&x)
{
	x=0;register bool z=true;
	register char c=getchar_unlocked();
	for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
	for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
	x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
string s; int tot,a[N],cnt[N],b[N][20];
bool big_than_0(string &s) {return s.size()>1||(s.size()==1&&s.back()!='0');}
void chu_2(string &s)
{
	reverse(s.begin(),s.end());
	for(int i=s.size()-1,now=0;i>=0;i--)
	{
		int x=s[i]-'0'+now*10,tmp=x>>1;
		now=x-(tmp<<1),s[i]=tmp+'0';
	}
	while(s.size()>1&&s.back()-'0'==0) s.pop_back();
	reverse(s.begin(),s.end());
}
void output(int x)
{
	putchar_unlocked('2');
	if(x==1) return ; putchar_unlocked('(');
	if(x==0) return putchar_unlocked('0'),putchar_unlocked(')'),void();
	memset(b[x],0,sizeof(b[x])),cnt[x]=0;
	for(int i=0,y=x;y;i++,y>>=1) if(y&1) b[x][++cnt[x]]=i;
	reverse(b[x]+1,b[x]+1+cnt[x]);
	for(int i=1;i<=cnt[x];i++)
	{
		output(b[x][i]);
		if(i!=cnt[x]) putchar_unlocked('+');
	}
	putchar_unlocked(')');
}
signed main()
{
	freopen("pow.in","r",stdin),freopen("pow.out","w",stdout);
	cin>>s;
	for(int i=0;big_than_0(s);i++,chu_2(s))
		if((s.back()-'0')&1) a[++tot]=i;
	reverse(a+1,a+1+tot);
	for(int i=1;i<=tot;i++) 
	{
		output(a[i]); 
		if(i!=tot) putchar_unlocked('+');
	}
}

T2 子串的子串

  • 部分分 \(100pts\):每次查询都重构一遍后缀数组,最后答案为 \(\dfrac{len\times (len+1)}{2}-\sum\limits_{i=l}^r{height_i}\)\(O(nq\log(n))\)

  • 正解一:

    考虑 \(q\) 很大 \(n\) 很小,所以会存在大量右端点重复的,所以总共只需要跑 \(n\) 遍后缀数组,对于右端点相同根据 \(lcp(sa_l,sa_r)=\min\limits_{i=l+1}^r{height_i}\),套个 ST 表即可。

    可以将左端点排序套链表做,也可以每个询问都 \(O(n)\) 跑一遍,前者复杂度为 \(O(n^2\log(n)+q)\),后者为 \(O(n^2\log(n)+nq)\),排序可以用桶排,后者就能过。

    点击查看代码
    #include<bits/stdc++.h>
    #define ll long long 
    #define endl '\n'
    #define sort stable_sort
    #define pb push_back
    using namespace std;
    const int N=3010,M=2e4+10;
    template<typename Tp> inline void read(Tp&x)
    {
    	x=0;register bool z=true;
    	register char c=getchar_unlocked();
    	for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
    	for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
    	x=(z?x:~x+1);
    }
    template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
    template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
    template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
    template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
    int n,m,mx[N],sa[N],rk[N],id[N],pos[N],cnt[N],key[N],ans[M],oldrk[N],height[N],mi[15][N];
    char s[N],t[N];
    struct aa {int l,id;}; vector<aa>e[N];
    void count_sort(int len,int m)
    {
    	memset(cnt,0,4*(m+1));
    	for(int i=1;i<=len;i++) cnt[key[i]]++;
    	for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
    	for(int i=len;i>=1;i--) sa[cnt[key[i]]]=id[i],cnt[key[i]]--;
    }
    void init(char s[],int len)
    {
    	int m=127,tot=0,num=0;
    	for(int i=1;i<=len;i++) key[i]=rk[id[i]=i]=(int)s[i];
    	count_sort(len,m);
    	for(int w=1;w<len&&tot!=len;w<<=1,m=tot)
    	{
    		num=0;
    		for(int i=len;i>=len-w+1;i--) id[++num]=i;
    		for(int i=1;i<=len;i++) if(sa[i]>w) id[++num]=sa[i]-w;
    		for(int i=1;i<=len;i++) key[i]=rk[id[i]];
    		count_sort(len,m); for(int i=1;i<=len;i++) oldrk[i]=rk[i];
    		tot=0; for(int i=1;i<=len;i++)
    		{
    			tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
    			rk[sa[i]]=tot;
    		}
    	}
    	for(int i=1,k=0;i<=len;i++) if(rk[i])
    	{
    		for(k-=(!!k);s[i+k]==s[sa[rk[i]-1]+k];k++);
    		height[rk[i]]=k;
    	}
    	memset(mi,0x3f,sizeof(mi)),memset(cnt,0,sizeof(cnt));
    	for(int i=1;i<=len;i++) mi[0][i]=height[i];
    	for(int i=1;i<=__lg(len);i++) for(int j=1;j<=len;j++)
    		mi[i][j]=min(mi[i-1][j],mi[i-1][j+(1<<(i-1))]);
    }
    int lcp(int l,int r)
    {l++; int t=__lg(r-l+1); return min(mi[t][l],mi[t][r-(1<<t)+1]);}
    signed main()
    {
    	freopen("substring.in","r",stdin),freopen("substring.out","w",stdout);
    	read(n,m),scanf("%s",s+1); 
    	for(int i=1;i<=n;i++) mx[i]=i+1;
    	for(int i=1,l,r;i<=m;i++) read(l,r),e[r].pb((aa){l,i}),mx[r]=min(mx[r],l);
    	for(int r=1;r<=n;r++)
    	{
    		memset(t,0,sizeof(t));
    		for(int i=mx[r];i<=r;i++) t[i-mx[r]+1]=s[i];
    		init(t,r-mx[r]+1);
    		for(auto x:e[r])
    		{
    			int l=x.l,id=x.id,tot=0; ans[id]=(r-l+1)*(r-l+2)>>1;
    			for(int i=l-mx[r]+1;i<=r-mx[r]+1;i++) pos[++tot]=rk[i];
    			for(int i=1;i<=tot;i++) cnt[pos[i]]++; tot=0;
    			for(int i=1;i<=r-mx[r]+1;i++) for(;cnt[i];cnt[pos[++tot]=i]--);
    			for(int i=2;i<=tot;i++) ans[id]-=lcp(pos[i-1],pos[i]);
    		}
    	}
    	for(int i=1;i<=m;i++) write(ans[i]),puts("");
    }
    
  • 正解二(官方正解):

    统计每个 \([l,r]\) 对答案的贡献,\(O(n^2)\) 预处理,\(O(1)\) 查询。

    对于每个 \([l,r]\),计算其哈希值,会有重复的,所以 \(ans_{l,r}+1,ans_{last_{hash(l,r)},r}-1\),最后二维前缀和一下即可。

    复杂度 \(O(n^2+q)\) 甚至没有后缀数组跑得快。

    点击查看代码
    #include<bits/stdc++.h>
    #define ll long long
    #define ull unsigned long long
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=3010,B=29;
    template<typename Tp> inline void read(Tp&x)
    {
    	x=0;register bool z=true;
    	register char c=getchar_unlocked();
    	for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
    	for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
    	x=(z?x:~x+1);
    }
    template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
    template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
    template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
    template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
    int n,m,ans[N][N]; char s[N]; 
    unordered_map<ull,int>last; ull tmp,b[N],h[N];
    void get_hash(char s[])
    {
    	b[0]=1; for(int i=1;i<=n;i++) b[i]=b[i-1]*B;
    	for(int i=1;i<=n;i++) h[i]=h[i-1]*B+(s[i]-'a');
    }
    ull ask(int l,int r) {return h[r]-h[l-1]*b[r-l+1];}
    signed main()
    {
    	freopen("substring.in","r",stdin),freopen("substring.out","w",stdout);
    	read(n,m),scanf("%s",s+1),get_hash(s);
    	for(int i=1,vr=n;i<=n;i++,vr--,last.clear())
    		for(int l=1,r=i;r<=n;l++,r++)
    			ans[last[tmp=ask(l,r)]][r]--,ans[l][r]++,last[tmp]=l;
    	for(int l=n;l>=1;l--) for(int r=l;r<=n;r++)
    		ans[l][r]+=ans[l+1][r]+ans[l][r-1]-ans[l+1][r-1];
    	for(int i=1,l,r;i<=m;i++) read(l,r),write(ans[l][r]),puts("");
    }
    

T3 魔法咒语

建两棵trie 树,原串建一棵,反串建一棵。

那么在原树上遍历到节点 \(p\) 时,若他没有 \(c\) 儿子,就将反树中 \(c\) 的贡献加上。

这样会有两种情况考虑不到,一是原串可以被其自身前 \(len-1\) 个字符和最后一个字符拼出来,一是只有一个字符的串不可能被拼出来,特殊处理一下即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e4+10,M=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
	x=0;register bool z=true;
	register char c=getchar_unlocked();
	for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
	for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
	x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
int n; ll ans; bool vis[26],yet[26];
struct trie
{
	int tot,son[M][26],cnt[26];
	void insert(char s[],int len,bool d)
	{
		if(d) reverse(s,s+len);
		for(int i=0,p=0,c;i<len;i++)
		{
			if(!son[p][c=s[i]-'a']) son[p][c]=++tot,cnt[c]++;
			p=son[p][c];
		}
	}
}pre,suf;
signed main()
{
	freopen("magic.in","r",stdin),freopen("magic.out","w",stdout);
	read(n); char s[45];
	for(int i=1,len;i<=n;i++) 
	{
		scanf("%s",s),len=strlen(s),vis[s[len-1]-'a']=1;
		pre.insert(s,len,0),suf.insert(s,len,1);
		if(len==1&&!yet[s[0]-'a']) ans+=(yet[s[0]-'a']=1);
	}
	for(int p=1;p<=pre.tot;p++) for(int c=0;c<26;c++)
		(!pre.son[p][c])?ans+=suf.cnt[c]:ans+=vis[c];
	write(ans);
}

T4 表达式

对于模数很小的情况,我们可以建一棵线段树,\(val_{p,x}\) 表示在线段树节点 \(p\) 管辖区间内,输入的树是 \(x\) 的结果,显然有 pushup 为 \(val_{p,x}=val_{re,val_{ls,x}}\),即先放到左边跑,再放到右边跑,叶子结点特殊处理。

那么对于模数很大的,发现数据中模数都是合数或很小的质数(前三个点暴力除外),考虑将其分解成几个较小的数的乘积,对于每个分解出的数跑线段树,最后用 crt 合并答案即可。

实现细节上,需要扩展欧拉定理,不然快速幂指数太大复杂度爆炸,所以要求出每个分解出的数的欧拉函数,由此求逆元又可以用快速幂了,不需要 exgcd,这样快速幂的 \(\log\) 直接变成常数级别,跑得飞快。

甚至为了做这题专门学了 crt 和扩欧。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
	x=0;register bool z=true;
	register char c=getchar_unlocked();
	for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
	for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
	x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
int _,n,m,P,a[N],mod[5],phi[5],inv[5],val[N<<2][5][30]; char s[N];
ll qpow(ll a,int b)
{ll res=1; for(;b;(a*=a)%=P,b>>=1) if(b&1) (res*=a)%=P; return res;}
int qpow(int a,int b,int p)
{int res=1; for(;b;(a*=a)%=p,b>>=1) if(b&1) (res*=a)%=p; return res;}
void pushup(int p)
{
	for(int i=1;i<=mod[0];i++) for(int j=0;j<mod[i];j++)
		val[p][i][j]=val[rs][i][val[ls][i][j]];
}
void calc(int p,int x)
{
	if(s[x]=='+') for(int i=1,tmp;i<=mod[0];i++)
	{
		tmp=a[x]%mod[i];
		for(int j=0;j<mod[i];j++) val[p][i][j]=(j+tmp)%mod[i];
	}
	else if(s[x]=='*') for(int i=1,tmp;i<=mod[0];i++)
	{
		tmp=a[x]%mod[i];
		for(int j=0;j<mod[i];j++) val[p][i][j]=j*tmp%mod[i];
	}
	else for(int i=1,tmp;i<=mod[0];i++)
	{
		tmp=a[x]?a[x]%phi[i]+phi[i]:0;
		for(int j=0;j<mod[i];j++) val[p][i][j]=qpow(j,tmp,mod[i]);
	}
}
void build(int p,int l,int r)
{
	if(l==r) return calc(p,l),void();
	build(ls,l,mid),build(rs,mid+1,r),pushup(p);
}
void change(int p,int l,int r,int x)
{
	if(l==r) return calc(p,l),void();
	x<=mid?change(ls,l,mid,x):change(rs,mid+1,r,x),pushup(p);
}
signed main()
{
	freopen("expr.in","r",stdin),freopen("expr.out","w",stdout);
	read(_,n,m,P); for(int i=1;i<=n;i++) 
	{
		while(s[i]!='+'&&s[i]!='*'&&s[i]!='^') s[i]=getchar_unlocked();
		read(a[i]);
	}
	if(_<=3)
	{
		ll x; for(int i=1,op;i<=m;i++)
		{
			read(op,x);
			if(op&1)
			{
				for(int j=1;j<=n;j++)
					s[j]=='+'?(x+=a[j])%=P:(s[j]=='*'?(x*=a[j])%=P:x=qpow(x,a[j]));
				write(x),puts("");
			}
			else
			{
				for(s[x]=0;s[x]!='+'&&s[x]!='*'&&s[x]!='^';) 
					s[x]=getchar_unlocked();
				read(a[x]);
			}
		}
		return 0;
	}
	int x=P; for(int i=2,tmp=sqrt(P),sum;i<=tmp;i++) if(!(x%i))
	{
		for(sum=1;!(x%i);x/=i) sum*=i;
		mod[++mod[0]]=sum,phi[mod[0]]=sum/i*(i-1);
	}
	if(x>1) mod[++mod[0]]=x,phi[mod[0]]=x-1;
	for(int i=1;i<=mod[0];i++) inv[i]=qpow(P/mod[i],phi[i]-1,mod[i]);
	build(1,1,n); for(int i=1,op,x;i<=m;i++)
	{
		read(op,x);
		if(op&1)
		{
			int res=0; for(int j=1;j<=mod[0];j++)
				(res+=P/mod[j]*inv[j]%P*val[1][j][x%mod[j]]%P)%=P;
			write(res),puts("");
		}
		else
		{
			for(s[x]=0;s[x]!='+'&&s[x]!='*'&&s[x]!='^';) 
				s[x]=getchar_unlocked();
			read(a[x]),change(1,1,n,x);
		}
	}
}
posted @ 2024-10-10 09:49  卡布叻_周深  阅读(19)  评论(0编辑  收藏  举报