多校A层冲刺NOIP2024模拟赛04
T1、02表示法
竟然有出题人敢出高精度(其实只是一个把string转成01串),开场看出记搜后十分犹豫到底要不要写高精,徘徊很久还是写了个小高精。
码(
/*
GGrun
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=2e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;
inline ll read(){
char c=getchar();ll x=0,f=1;
while(!isdigit(c))f=c=='-'?-1:1,c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
string f[1010];
bool vis[1010];
inline string sol(int man){
if(vis[man])return f[man];
vis[man]=1;
for(int i=20;i>=0;--i){
if((man>>i)&1){
if(i==0)f[man]+="2(0)+";
else if(i==1)f[man]+="2+";
else f[man]=f[man]+"2("+sol(i)+")+";
}
}
f[man].pop_back();
return f[man];
}
string ans,s;
inline void chu(string &s){
int len=s.size();
for(int i=0;i<len;++i){
if((s[i]-'0')&1)s[i+1]+=10,s[i]--;
s[i]=((s[i]-'0')>>1)+'0';
}
if(s[0]=='0')s.erase(s.begin());
}
bool dan[N];
signed main(){
// #ifndef ONLINE_JUDGE
freopen("pow.in","r",stdin);
freopen("pow.out","w",stdout);
// #endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
vis[1]=vis[2]=1;f[1]="2(0)",f[2]="2";
cin>>s;
int len=0;
while(s.size()){
if((s[s.size()-1]-'0')&1)dan[len]=1,--s[s.size()-1];
chu(s);
++len;
// cout<<s<<endl;
}
for(int i=len-1;i>=0;--i){
if(dan[i]){
if(i==0)ans+="2(0)+";
else if(i==1)ans+="2+";
else ans=ans+"2("+sol(i)+")+";
}
}
ans.pop_back();
cout<<ans;
}
T2、子串的子串
典型的扫描线,套路转化成pre,直接哈希用unordered_map记一下就行。
然后发现其实不行!!!!!!!!!
卡了半天,没想到怎么处理哈希冲突,后来T3给了点启发(只不过启发的不是正解),想到枚举的字符串每次只差一个字符,于是就可以用trie树去记他们的pre。
然后就炸空间了!
后来硬生生卡到了90分,空间还是开不下。
好吧,说说正解,其实注意到大部分字符串的长度不一样,长度也是他们本身的key值,可以用来减少哈希冲突,所以还是用unordered_map,每次枚举长度相同的字符串,然后就可以了。
码(
/*
GGrun
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=2e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,B=123;
inline ll read(){
char c=getchar();ll x=0,f=1;
while(!isdigit(c))f=c=='-'?-1:1,c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int n,q;
char s[5000];
ull hs[5000],ci[5000];
unordered_map<ull,int > pre;
inline ull hx(int l,int r){
return (hs[r]-hs[l-1]*ci[r-l+1]);
}
int c[3005];
inline void add(int x,int y){while(x<=n)c[x]+=y,x+=x&-x;}
inline int ask(int x){int ans=0;while(x)ans+=c[x],x^=x&-x;return ans;}
struct jj{
int l,r,id;
inline bool operator <(const jj&x)const{return l<x.l;}
}Q[N];
struct xian{
int l,v;
};
vector<xian> sao[3005];
int ans[N];
signed main(){
// #ifndef ONLINE_JUDGE
freopen("substring.in","r",stdin);
freopen("substring.out","w",stdout);
// #endif
// double ti=clock();
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
n=read(),q=read();
scanf("%s",s);
ci[0]=1;
for(int i=0;i<n;++i){
hs[i+1]=(hs[i]*B+s[i]);
ci[i+1]=ci[i]*B;
}
for(int i=1;i<=q;++i){
Q[i].l=read(),Q[i].r=read(),Q[i].id=i;
}
sort(Q+1,Q+1+q);
for(int len=1;len<=n;++len){
pre.clear();
for(int i=1,j=i+len-1;j<=n;++j,++i){
ull man=hx(i,j);
sao[i+1].ps({j,-1});sao[pre[man]+1].ps({j,1}),pre[man]=i;
}
}
for(int i=0,j=1;i<=n;++i){
for(auto k:sao[i]){
add(k.l,k.v);
}
while(j<=q&&Q[j].l==i)
ans[Q[j].id]=ask(Q[j].r),++j;
}
for(int i=1;i<=q;++i)
cout<<ans[i]<<'\n';
}
听说还可以用SAM做,不会,run了。
T3、魔法咒语
肯定得用trie树,先正着插一遍,能却确定不同的前缀有多少个,倒着插一遍能确定不同的后缀有多少个。把他们乘起来,再加上长度唯一的字符串(不重)的个数就是可能的方案数。
但是还要去重,怎么去呢,一个字符串会被不同前后缀组成多次,说明中间有一些字符即可以从前缀的来,也可以从后缀得来。所以对于一个字符,如果他既在前缀trie树中出现过,又在后缀trie树中出现过,那么这个字符便有了到底应该在前面出现还是应该在后面出现的选择,去重便要减一。
但是还有规定,前后两个字符串不能为空,那么对于两颗trie树来说,在第一层(根的儿子们)是必选的,他们不能计数。除去这些点后,每种小写字母在两棵树中的出现次数分别是 $ cnt1_i , cnt2_i $
那么最后答案再减去 $ \sum_{i ∈ 小写字母 }{cnt1_i * cnt2_i} $ 即可。
码(
/*
GGrun
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=2e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;
inline ll read(){
char c=getchar();ll x=0,f=1;
while(!isdigit(c))f=c=='-'?-1:1,c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int n,cnt,son[10001*40][26],num1[26],num2[26];
string s[10001];
bool f[10001*40],dan[26];
inline void ins(string &s){
int now=0,len=s.size();
for(int i=0;i<len;++i){
if(!son[now][s[i]-'a'])son[now][s[i]-'a']=++cnt;
f[now]=1;
now=son[now][s[i]-'a'];
}
}
inline void sol(int num[],int now){
for(int i=0;i<26;++i)
if(son[now][i])num[i]+=now!=0,sol(num,son[now][i]);
}
signed main(){
// #ifndef ONLINE_JUDGE
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
// #endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
cin>>s[i],ins(s[i]);
ll ans=0,cnt1=cnt;
sol(num1,0);
for(int i=0;i<=cnt;++i){
for(int j=0;j<26;++j)
son[i][j]=0;
}
cnt=0;
for(int i=1;i<=n;++i){
// cout<<s[i].size()<<endl;
if(s[i].size()==1&&!dan[s[i][0]-'a'])++ans,dan[s[i][0]-'a']=1;
reverse(s[i].begin(), s[i].end()),ins(s[i]);
}
sol(num2,0);
ans+=cnt1*cnt;
for(int i=0;i<26;++i)
ans-=(ll)num1[i]*num2[i];
cout<<ans;
}
T4、表达式
用线段树维护函数答案。
但是还得用CRT,好吧其实数论那块我根本没学明白。场上骗了30分,写的分块,忘了写线段树更优了,痛失10分。
简单复习(贺)了一下
码(
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=1e6+10,inf=0x3f3f3f3f,B=sqrt(1e9);
const ll linf=0x3f3f3f3f3f3f3f3f;
inline ll read(){
char c=getchar_unlocked();ll x=0,f=1;
while(!isdigit(c))f=c=='-'?-1:1,c=getchar_unlocked();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar_unlocked();
return x*f;
}
int n,m,p,X[N],mod,t;
char op[N];
int ans[800005][8][30],ny[8],cntmod,mo[30];
inline ll qpow(ll x,ll y,ll mod){
ll ans=1;
while(y){
if(y&1)ans=ans*x%mod;
x=x*x%mod;y>>=1;
}
return ans;
}
inline void ad(int k,int pos){
if(op[pos]=='+'){
for(int i=1;i<=cntmod;++i){
ll mod=mo[i];
for(int j=0;j<mod;++j)
ans[k][i][j]=(j+X[pos])%mod;
}
}
else if(op[pos]=='*'){
for(int i=1;i<=cntmod;++i){
ll mod=mo[i];
for(int j=0;j<mod;++j)
ans[k][i][j]=((ll)j*X[pos])%mod;
}
}
else{
for(int i=1;i<=cntmod;++i){
ll mod=mo[i];
for(int j=0;j<mod;++j)
ans[k][i][j]=qpow(j,X[pos],mod);
}
}
}
inline void jian(int k,int l,int r){
if(l==r){
ad(k,l);
return;
}
int mid=l+r>>1;
jian(k<<1,l,mid),jian(k<<1|1,mid+1,r);
for(int i=1;i<=cntmod;++i){
ll mod=mo[i];
for(int j=0;j<mod;++j)
ans[k][i][j]=ans[k<<1|1][i][ans[k<<1][i][j]];
}
}
inline void add(int k,int l,int r,int pos){
if(l==r){
ad(k,l);
return;
}
int mid=l+r>>1;
pos<=mid?add(k<<1,l,mid,pos):add(k<<1|1,mid+1,r,pos);
for(int i=1;i<=cntmod;++i){
ll mod=mo[i];
for(int j=0;j<mod;++j)
ans[k][i][j]=ans[k<<1|1][i][ans[k<<1][i][j]];
}
}
signed main(){
freopen("expr.in","r",stdin);
freopen("expr.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
t=read();
n=read(),m=read(),mod=read();
for(int i=1;i<=n;++i){
scanf(" %c",&op[i]);X[i]=read();
}
if(t<=3){
for(int i=1,opp,pos,x;i<=m;++i){
opp=read();
if(opp==1){
x=read()%mod;
for(int j=1;j<=n;++j){
if(op[j]=='+')x=(x+X[j])%mod;
else if(op[j]=='*')x=(ll)x*X[j]%mod;
else x=qpow(x,X[j],mod);
}
cout<<x<<'\n';
}
else pos=read(),op[pos]=getchar_unlocked(),X[pos]=read();
}
}
else{
ll p=mod;
for(int i=2;i*i<=mod;++i){
if(p%i==0){
ll op=1;
while(p%i==0)op=op*i,p/=i;
mo[++cntmod]=op;
for(int j=1,man=mod/op;j<op;++j)
if((ll)man*j%op==1){ny[cntmod]=j;break;}
}
}
if(p>1){
mo[++cntmod]=p;
for(int j=1,man=mod/p;j<p;++j)
if((ll)man*j%p==1){ny[cntmod]=j;break;}
}
jian(1,1,n);
for(int i=1,opp,pos,x;i<=m;++i){
opp=read();
if(opp==1){
x=read();
ll op=0;
for(int j=1;j<=cntmod;++j){
op=(op+(ll)mod/mo[j]*ny[j]%mod*ans[1][j][x%mo[j]]%mod)%mod;
}
cout<<op<<'\n';
}
else{
pos=read();op[pos]=getchar_unlocked();X[pos]=read();
add(1,1,n,pos);
}
}
}
}
总结
这场题主要是T2学了个unordered_map如何更优秀的避免哈希冲突,那就是多运用它们本身自带的key值。
T4其实感觉线段树部分会了,但不知道下次还会不会用CRT了。
P
其实我没有图片(