2022/8/1 启智树考试总结
不能贴贴题单了(悲)
所以把题面一起粘上来吧。
A.Doughnut
题目描述
Aloisia 有很多很多甜甜圈。有一天,她在地上画了 n+1 个格子,想从第 1 个格子跳到第 n+1 个格子。规则是,Aloisia 每到一个格子,都会放一个甜甜圈在里面。在第 i 个格子时,如果当前甜甜圈的数量是偶数,那么她会跳到第 i+1 个格子;否则她会跳到第 Pi 个格子(1≤Pi≤i)。
若她到达了第 n+1 个格子,则立即结束。Aloisia 想知道,她要跳多少次才能到达终点。答案对 1000000007 取模。
输入
输入的第一行包含一个正整数 n,表示一共有 n+1 个格子;第二行包含 n 个正整数 Pi,含义如题目描述中所述。
输出
输出一个整数,表示 Aloisia 需要跳的次数,对 1000000007 取模。
Solution
-
\(\mathtt{DP}\) 题。设 \(f_i\) 表示从 1 走到 \(\mathtt{i}\) 所需要的最小步数;
-
由题目可以得到一个非常好的性质,那就是如果我走到了 \(i\),那么从 \(1~i-1\) 中的每个点肯定都经过了偶数次(因为如果经过是奇数次的话会到达 \(p_i\),而题目有 \(1\le p_i\le i\));
-
那么我们可以写出方程:\(f_i=f_{i-1}+(f_{i-1}-f_{p_{i-1}})+2\);
-
考场上挂了分,单纯因为没开 \(\mathtt{long\ long}\) 以及没取模……
AC code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=1e6+10;
const int mod=1e9+7;
int p[N];
long long f[N];
int n;
int main(){
n=read();
for(int i=1;i<=n;++i)
p[i]=read();
for(int i=2;i<=n+1;++i)
(f[i]=0ll+f[i-1]+2+(f[i-1]-f[p[i-1]]))%=mod;
printf("%lld",(f[n+1]+mod)%mod);
return 0;
}
B.Pudding
题目描述
Harry 得到了一个字符串。他想让 Aloisia 找出其中包含的 (0w0) 子序列有多少个。(注意 0 是数字,不是字母 O)为了提高难度,Harry 提出他可以随时修改一个或一段字符,询问区间内的 (0w0) 子序列个数。答案对 4294967296 取模。如果答对了的话,他就请 Aloisia 喝布丁奶茶。请你帮帮 Aloisia。
输入
输入的第一行包含两个整数 n、m,分别表示字符串的长度和操作的数量;
第二行包含一个长度为 n 的字符串,表示一开始的字符串;
接下来 m 行,每行一个操作,操作的格式如下:
1.A x y:表示把字符串的第 x 位改成字符 y。字符串元素从 1 开始标号。
2.B x y z:表示把字符串的第 x 位到第 y 位都改成字符 z。
3.C x y:表示询问字符串的区间 [x,y] 内有多少个子序列等于 (0w0)。
输出
输出若干行。对于每个 C 操作,输出一行,包含一个整数,表示该询问的答案,对 4294967296 取模。
Solution
如何得到60分
-
其他都基本朴(暴)素(力)处理,把求子序列的需求用 \(\mathtt{DP}\) 处理掉;
-
能得到答案 \(<mod\) 的60分;
60pts code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
#define ll long long
#define id(i) ((i-1)/dx+1)
const int N=5e4+10;
const ll mod=4294867296;
int n,m,dx;
char st[N];
ll f[N][6];
char tg[250];
char s(int x){
if(tg[id(x)]) return tg[id(x)];
return st[x];
}
void change(int l,int r,char v){
int p=id(l),q=id(r);
if(p==q){
for(int i=l;i<=r;++i)
st[i]=v;
return ;
}
if(tg[p]){
change((p-1)*dx+1,p*dx,tg[p]);
tg[p]=0;
}
for(int i=l;i<=p*dx;++i)
st[i]=v;
for(int i=p+1;i<q;++i)
tg[i]=v;
if(tg[q]){
change((q-1)*dx+1,min(n,q*dx),tg[q]);
tg[q]=0;
}
for(int i=(q-1)*dx+1;i<=r;++i)
st[i]=v;
return ;
}
void ask(int l,int r){
memset(f,0,sizeof(f));
for(int i=l;i<=r;++i){
(f[i-l+1][0]=f[i-l][0]+(s(i)=='('))%=mod;
(f[i-l+1][1]=f[i-l][1]+1ll*(s(i)=='0')*f[i-l][0])%=mod;
(f[i-l+1][2]=f[i-l][2]+1ll*(s(i)=='w')*f[i-l][1])%=mod;
(f[i-l+1][3]=f[i-l][3]+1ll*(s(i)=='0')*f[i-l][2])%=mod;
(f[i-l+1][4]=f[i-l][4]+1ll*(s(i)==')')*f[i-l][3])%=mod;
}
printf("%lld\n",f[r-l+1][4]);
return ;
}
int main(){
n=read(),m=read();
scanf("%s",st+1);
dx=sqrt(n);
char opt,v;
int x,y;
while(m--){
cin>>opt,x=read();
if(opt=='A'){
cin>>v;
st[x]=v;
}
else if(opt=='B'){
y=read(),cin>>v;
change(x,y,v);
}
else{
y=read();
ask(x,y);
}
}
return 0;
}//属于是暴力?
正解
-
线段树。用线段树处理单点修改和区间修改,并给每个节赋一个 \(5\times 5\) 大小的数组,用于统计 \(\mathtt{(0w0)}\) 的每一个子段在线段树的这一段里出现的次数;
-
状态转移时和 \(\mathtt{DP}\) 很相似;
-
第一发没过居然是因为 \(mod\) 打错了……
AC code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
#define ll long long
const int N=5e4+10;
const ll mod=4294967296;
int n,m;
char st[N];
struct memr{
int l,r;
char v,tg;
ll ans[5][5];
}tr[N<<3];
void add(int p,char v){
if(v=='(')
tr[p].ans[0][0]+=tr[p].r-tr[p].l+1;
else if(v=='0'){
tr[p].ans[1][1]+=tr[p].r-tr[p].l+1;
tr[p].ans[3][3]+=tr[p].r-tr[p].l+1;
}
else if(v=='w')
tr[p].ans[2][2]+=tr[p].r-tr[p].l+1;
else if(v==')')
tr[p].ans[4][4]+=tr[p].r-tr[p].l+1;
return ;
}
void mul(memr x,memr y,memr &a){
memset(a.ans,0,sizeof(a.ans));
for(int s=0;s<5;++s)
for(int e=s;e<5;++e){
for(int i=s;i<e;++i)
(a.ans[s][e]+=1ll*x.ans[s][i]*y.ans[i+1][e])%=mod;
(a.ans[s][e]+=0ll+x.ans[s][e]+y.ans[s][e])%=mod;
}
return ;
}
void pushup(int p){
memset(tr[p].ans,0,sizeof(tr[p].ans));
mul(tr[p<<1],tr[p<<1|1],tr[p]);
return ;
}
void pushdown(int p){
if(tr[p].tg){
tr[p<<1].tg=tr[p].tg;
tr[p<<1].v=tr[p].tg;
tr[p<<1|1].tg=tr[p].tg;
tr[p<<1|1].v=tr[p].tg;
memset(tr[p<<1].ans,0,sizeof(tr[p<<1].ans));
memset(tr[p<<1|1].ans,0,sizeof(tr[p<<1|1].ans));
add(p<<1,tr[p].tg),add(p<<1|1,tr[p].tg);
tr[p].tg=0;
}
return ;
}
void build(int p,int l,int r){
tr[p].l=l,tr[p].r=r;
if(l==r){
tr[p].v=st[l];
memset(tr[p].ans,0,sizeof(tr[p].ans));
add(p,st[l]);
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
return ;
}
void change(int p,int l,int r,char v){
if(l<=tr[p].l && tr[p].r<=r){
tr[p].tg=v;
tr[p].v=v;
memset(tr[p].ans,0,sizeof(tr[p].ans));
add(p,v);
return ;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) change(p<<1,l,r,v);
if(mid<r) change(p<<1|1,l,r,v);
pushup(p);
return ;
}
void ask(int p,int l,int r,memr &d){
if(l<=tr[p].l && tr[p].r<=r){
memr cnt=d;
mul(cnt,tr[p],d);
return ;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) ask(p<<1,l,r,d);
if(mid<r) ask(p<<1|1,l,r,d);
return ;
}
int main(){
n=read(),m=read();
scanf("%s",st+1);
build(1,1,n);
char opt,v;
int x,y;
while(m--){
cin>>opt,x=read();
if(opt=='A'){
cin>>v;
change(1,x,x,v);
}
else if(opt=='B'){
y=read(),cin>>v;
change(1,x,y,v);
}
else{
y=read();
memr cnt;
memset(cnt.ans,0,sizeof(cnt.ans));
ask(1,x,y,cnt);
printf("%lld\n",cnt.ans[0][4]);
}
}
return 0;
}