2021.10.26考试总结[冲刺NOIP模拟16]
T1 树上的数
\(DFS\)一遍。结构体存边好像更快?
\(code:\)
T1
#include<bits/stdc++.h>
using namespace std;
namespace IO{
int read(){
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
void write(int x,char sp){
char ch[20]; int len=0;
if(x<0) x=-x,putchar('-');
do{ ch[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
}
void ckmin(int& x,int y){ x=x<y?x:y; }
void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=5000010;
int n,m,a,b,f,q,x,y,sum,ans;
int idx,head[NN];
bool vis[NN];
struct edge{ int to,nex; }e[NN];
void dfs(int s){
if(vis[s]) return;
--sum; vis[s]=1;
for(int i=head[s],v=e[i].to;i;i=e[i].nex,v=e[i].to) dfs(v);
}
signed main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
sum=n=read(); m=read(); a=read(); b=read(); f=1;
for(int i=2;i<=n;++i){
e[++idx]=(edge){i,head[f]}; head[f]=idx;
f=((1ll*f*a+b)^19760817)%i+1;
}
q=read(); x=read(); y=read();
for(int i=1;i<=m;++i){
dfs(q); ans^=sum;
q=(((1ll*q*x+y)^19760817)^(i+1<<1))%(n-1)+2;
}
write(ans,'\n');
return 0;
}
T2 时代的眼泪
好像挺明显的换根\(DP\)。但我因为多组询问成功跑偏
考虑转换贡献,一个点的贡献即为它字数内权值小于它的点数,所有点的贡献之和即为答案。
首先不难求得\(1\)为根时的答案,接下来当根由\(x\)换成\(x\)的儿子\(y\)时,\(y\)的子树变为整棵树,\(x\)的子树变为以\(x\)为根时树中\(y\)子树外的部分。
可以树上主席树维护信息,但范围\(1e6\),主席树优秀的常数使它无法通过本题。可以用树状数组在\(DFS\)中记录遍历完子树后权值在某个范围的变化量来代替。
\(code:\)
T2
#include<bits/stdc++.h>
using namespace std;
namespace IO{
auto read=[]()->int{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
};
void write(long long x,char sp){
char ch[20]; int len=0;
if(x<0) x=-x,putchar('-');
do{ ch[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
}
void ckmin(int& x,int y){ x=x<y?x:y; }
void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=1000010;
int n,q,u,w[NN],wn[NN],wf[NN];
long long f[NN];
int idx,head[NN];
struct edge{ int to,nex; }e[NN<<1];
int ext,has[NN];
auto add=[](int a,int b)->void{
e[++idx]=(edge){b,head[a]}; head[a]=idx;
e[++idx]=(edge){a,head[b]}; head[b]=idx;
};
namespace BIT{
int c[NN];
auto insert=[](int pos,int x)->void{ while(pos<=n+1){ c[pos]+=x; pos+=pos&-pos; } };
auto query=[](int pos)->int{ int res=0; while(pos){ res+=c[pos]; pos-=pos&-pos; } return res; };
void DFS(int s,int fa){
f[1]+=query(n+1)-query(w[s]);
insert(w[s],1);
for(int i=head[s],v=e[i].to;i;i=e[i].nex,v=e[i].to)
if(v!=fa) DFS(v,s);
insert(w[s],-1);
}
void Dfs(int s,int fa){
wn[s]=-query(w[s]-1);
if(fa) wf[s]=-query(w[fa]-1);
insert(w[s],1);
for(int i=head[s],v=e[i].to;i;i=e[i].nex,v=e[i].to)
if(v!=fa) Dfs(v,s);
wn[s]+=query(w[s]-1);
if(fa) wf[s]+=query(w[fa]-1);
}
} using namespace BIT;
void dfs(int s,int fa){
for(int i=head[s],v=e[i].to;i;i=e[i].nex,v=e[i].to) if(v!=fa){
f[v]=f[s]-wn[v]+query(w[v]-1)-wf[v];
dfs(v,s);
}
}
signed main(){
freopen("tears.in","r",stdin);
freopen("tears.out","w",stdout);
n=read(); q=read();
for(int i=1;i<=n;i++)
w[i]=read(),has[i]=w[i];
sort(has+1,has+n+1); ext=unique(has+1,has+n+1)-has-1;
for(int i=1;i<=n;i++)
w[i]=lower_bound(has+1,has+ext+1,w[i])-has+1;
for(int a,b,i=1;i<n;i++)
a=read(),b=read(),add(a,b);
DFS(1,0); Dfs(1,0); dfs(1,0);
while(q--){
u=read();
printf("%lld\n",f[u]);
}
return 0;
}
T3 传统艺能
对这类问题有很经典的\(DP\)。设\(f_c\)表示以\(c\)结尾的本质不同子序列个数,有
\[f_c=1+\sum_{char}f_{char}
\]
这个转移是线性的,加上本题字符集只有\(3\),可以考虑矩阵优化。可以预处理出每个字符对应的转移矩阵,用线段树维护矩阵即可。
\(code:\)
T3
#include<bits/stdc++.h>
using namespace std;
namespace IO{
int read(){
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
void write(int x,char sp){
char ch[20]; int len=0;
if(x<0) x=-x,putchar('-');
do{ ch[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
}
void ckmin(int& x,int y){ x=x<y?x:y; }
void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=100010,mod=998244353;
int n,m,x,y,op,num[NN];
char t,ch[NN];
void add(int& a,int b){ a=(a+b>=mod)?(a+b-mod):(a+b); }
namespace segment_tree{
#define ld rt<<1
#define rd (rt<<1)|1
struct mat{
int s[4][4];
mat(){}
mat(int x){ memset(s,0,sizeof(s)); s[0][0]=s[1][1]=s[2][2]=s[3][3]=x; }
void print(){for(int i=0;i<=3;i++)for(int j=0;j<=3;j++)write(s[i][j],j==3?'\n':' ');puts("");}
mat operator*(const mat& a)const{
mat res=mat(0);
for(int i=0;i<=3;i++)
for(int k=0;k<=3;k++)
for(int j=0;j<=3;j++)
add(res.s[i][j],1ll*s[i][k]*a.s[k][j]%mod);
return res;
}
}st,ans,tmp,base[4],mt[NN<<2];
void init(){
st.s[0][0]=1;
base[1]=mat(1); base[2]=mat(1); base[3]=mat(1);
base[1].s[0][1]=base[1].s[1][1]=base[1].s[2][1]=base[1].s[3][1]=1;
base[2].s[0][2]=base[2].s[1][2]=base[2].s[2][2]=base[2].s[3][2]=1;
base[3].s[0][3]=base[3].s[1][3]=base[3].s[2][3]=base[3].s[3][3]=1;
}
void pushup(int rt){ mt[rt]=mt[ld]*mt[rd]; }
void build(int rt,int l,int r){
if(l==r) return mt[rt]=base[num[l]],void();
int mid=l+r>>1;
build(ld,l,mid);
build(rd,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int pos,int typ){
if(l==r) return mt[rt]=base[typ],void();
int mid=l+r>>1;
if(pos<=mid) update(ld,l,mid,pos,typ);
else update(rd,mid+1,r,pos,typ);
pushup(rt);
}
mat query(int rt,int l,int r,int opl,int opr){
if(l>=opl&&r<=opr) return mt[rt];
int mid=l+r>>1;
mat res=mat(1);
if(opl<=mid) res=res*query(ld,l,mid,opl,opr);
if(opr>mid) res=res*query(rd,mid+1,r,opl,opr);
return res;
}
} using namespace segment_tree;
signed main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
n=read(); m=read();
scanf("%s",ch+1);
for(int i=1;i<=n;i++)
num[i]=ch[i]-'A'+1;
init(); build(1,1,n);
while(m--){
op=read();
if(op==1){
x=read(); cin>>t;
update(1,1,n,x,t-'A'+1);
} else{
x=read(); y=read();
ans=st*query(1,1,n,x,y);
add(ans.s[0][1],ans.s[0][2]); add(ans.s[0][1],ans.s[0][3]);
printf("%d\n",ans.s[0][1]);
}
}
return 0;
}
T4 铺设道路
考虑通过差分将区间问题转化为单点问题。令\(b_i=d_{i-1}-d_i\),那么每次操作就是选取\(l,r\),使\(b_l-1,b_r+1\),代价为\((r-l)^2\),最终要将所有\(b\)置为\(0\)。(包括\(b_{n+1}\))
要使时间最短,就要保证每次操作都是有效的,即\(b_l>0,b_r<0\)。
可以维护一个队列,扫一遍\(b\),遇到\(b>0\)则插入队列,\(b<0\)则找队中元素将其消去。因为\(d\geq 0\),所以一定能消完。
要求最大值则优先消队尾元素,反之优先消队尾元素。
\(code:\)
T4
#include<bits/stdc++.h>
using namespace std;
namespace IO{
int read(){
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
void write(int x,char sp){
char ch[20]; int len=0;
if(x<0) x=-x,putchar('-');
do{ ch[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
}
void ckmin(int& x,int y){ x=x<y?x:y; }
void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=100010,mod=998244353;
int n,m,x,y,op,num[NN];
char t,ch[NN];
void add(int& a,int b){ a=(a+b>=mod)?(a+b-mod):(a+b); }
namespace segment_tree{
#define ld rt<<1
#define rd (rt<<1)|1
struct mat{
int s[4][4];
mat(){}
mat(int x){ memset(s,0,sizeof(s)); s[0][0]=s[1][1]=s[2][2]=s[3][3]=x; }
void print(){for(int i=0;i<=3;i++)for(int j=0;j<=3;j++)write(s[i][j],j==3?'\n':' ');puts("");}
mat operator*(const mat& a)const{
mat res=mat(0);
for(int i=0;i<=3;i++)
for(int k=0;k<=3;k++)
for(int j=0;j<=3;j++)
add(res.s[i][j],1ll*s[i][k]*a.s[k][j]%mod);
return res;
}
}st,ans,tmp,base[4],mt[NN<<2];
void init(){
st.s[0][0]=1;
base[1]=mat(1); base[2]=mat(1); base[3]=mat(1);
base[1].s[0][1]=base[1].s[1][1]=base[1].s[2][1]=base[1].s[3][1]=1;
base[2].s[0][2]=base[2].s[1][2]=base[2].s[2][2]=base[2].s[3][2]=1;
base[3].s[0][3]=base[3].s[1][3]=base[3].s[2][3]=base[3].s[3][3]=1;
}
void pushup(int rt){ mt[rt]=mt[ld]*mt[rd]; }
void build(int rt,int l,int r){
if(l==r) return mt[rt]=base[num[l]],void();
int mid=l+r>>1;
build(ld,l,mid);
build(rd,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int pos,int typ){
if(l==r) return mt[rt]=base[typ],void();
int mid=l+r>>1;
if(pos<=mid) update(ld,l,mid,pos,typ);
else update(rd,mid+1,r,pos,typ);
pushup(rt);
}
mat query(int rt,int l,int r,int opl,int opr){
if(l>=opl&&r<=opr) return mt[rt];
int mid=l+r>>1;
mat res=mat(1);
if(opl<=mid) res=res*query(ld,l,mid,opl,opr);
if(opr>mid) res=res*query(rd,mid+1,r,opl,opr);
return res;
}
} using namespace segment_tree;
signed main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
n=read(); m=read();
scanf("%s",ch+1);
for(int i=1;i<=n;i++)
num[i]=ch[i]-'A'+1;
init(); build(1,1,n);
while(m--){
op=read();
if(op==1){
x=read(); cin>>t;
update(1,1,n,x,t-'A'+1);
} else{
x=read(); y=read();
ans=st*query(1,1,n,x,y);
add(ans.s[0][1],ans.s[0][2]); add(ans.s[0][1],ans.s[0][3]);
printf("%d\n",ans.s[0][1]);
}
}
return 0;
}