NOIP模拟赛(10.17):语言,色球,斐波,偶数
语言
题面:
牛妹正在学习一种新的语言,在这种语言里,单词只有形容词(\(\texttt{A}\)),名词(\(\texttt{N}\))和动词(\(\texttt{V}\))三种词性。但是一个单词可以对应多种词性。
一个名词性词组(\(\texttt{NP}\))可以由一个名词(\(\texttt{N}\)),或者一个形容词修饰一个子名词性词组(\(\texttt{A} + \texttt{NP}_1\)),或者两个子名词性词组(\(\texttt{NP}_1 + \texttt{NP}_2\))组成。即:
一个句子(\(\texttt{S}\))必须由一个名词性词组(\(\texttt{NP}_1\))加一个动词(\(\texttt{V}\))再加一个名词性词组(\(\texttt{NP}_2\))组成,即:
牛妹用这个语言写下一个单词的序列,现在你想知道这个单词序列是否能通过适当安排序列里每个单词的词性使之成为一个句子(不同位置的相同的单词也可以安排不同的词性)。
为了简单起见,她把每个单词编码对应为一个小写拉丁字母,不同的单词对应不同的字母(这里我们假设序列里面不同的单词的总数不超过 \(26\) 个)。每个单词用 \(1(001_{(2)})\) 到 \(7(111_{(2)})\) 来表示这个单词的词性。数字的二进制第 \(1\) 位为 \(1\) 表示这个单词可以作为形容词(\(\texttt{A}\)),否则表示无法作为形容词;第 \(2\) 位为 \(1\) 表示这个单词可以作为名词(\(\texttt{N}\)),否则表示无法作为名词;第 \(3\) 位为 \(1\) 表示这个单词可以作为动词(\(\texttt{V}\)),否则表示无法作为动词。
具体的对应关系如下表所示:
编码 | 词性 |
---|---|
\(1\) | \(\texttt{A}\) |
\(2\) | \(\texttt{N}\) |
\(3\) | \(\texttt{A} \text{ or } \texttt{N}\) |
\(4\) | \(\texttt{V}\) |
\(5\) | \(\texttt{A} \text{ or } \texttt{V}\) |
\(6\) | \(\texttt{N} \text{ or } \texttt{V}\) |
\(7\) | \(\texttt{A} \text{ or } \texttt{N} \text{ or } \texttt{V}\) |
题解:
$O(Tn)$做法 ($100$pts):
注意到一个句子中动词有且仅有一个,所以对于类型4单词数大于1的情况,直接输出NO即可
根据题意,只要是一个不包含动词且以名词结尾的字符串则一定可以视为一个名词词组(即$NP$)
发现动词前必须为名词类型,结尾必须为名词类型
所以枚举动词位置,判断动词前及句末是否有名词即可,若存在类型4且数量为一,则动词一定为类型4单词
Code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int w[26],a[N],n,cnt,pos;
string s1;
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
// freopen("language.in","r",stdin);
// freopen("language.out","w",stdout);
int t;
cin>>t;
while(t--){
cnt=0,pos=0;
for(int i=0;i<26;i++)cin>>w[i];
cin>>s1;
n=s1.length();
for(int i=1;i<=n;i++){
a[i]=w[s1[i-1]-'a'];
if(a[i]==4)cnt++,pos=i;//记录类型4单词数量
}
if(a[n]==1||a[n]==4||a[n]==5||cnt>1){
cout<<"No\n";
continue;
}
if(cnt==1){
if(a[pos-1]==0||a[pos-1]==1||a[pos-1]==5){
cout<<"No\n";
continue;
}else {
cout<<"Yes\n";
continue;
}
}
bool suc=1;
for(int i=2;i<n;i++){
if((a[i]==5||a[i]==6||a[i]==7)&&(a[i-1]==2||a[i-1]==3||a[i-1]==6||a[i-1]==7)){
cout<<"Yes\n";//枚举动词位置
suc=0;
break;
}
}
if(suc)cout<<"No\n";
}
return 0;
}
色球
题面:
牛牛有 \(n\) 种颜色的彩色小球(编号 \(1\) 到 \(n\)),每种颜色的小球他都有无限多个。他还有 \(n\) 个球桶(编号 \(1\) 到 \(n\)),球桶的内径与小球直径相当且高度是无限大的,因此能容纳无限多的小球。他想用这些小球和球桶玩游戏。
一开始这些球桶都是空的,紧接着他会依次做 \(m\) 个操作,每个操作都是以下 \(3\) 种操作中的一个:
push x y z
,表示把 \(x\) 个颜色为 \(y\) 的彩色小球放到第 \(z\) 个桶的最上面;pop x z
,表示把最上面的 \(x\) 个小球从第 \(z\) 个桶内拿出来;put u v
,表示把第 \(u\) 个桶的所有小球依次从顶部拿出放入第 \(v\) 个桶内。
现在他已经确定好了这 \(m\) 个操作都是什么,但在他开始玩之前,他想知道每次他进行第二类操作取出的最后一个小球是什么颜色。
题解:
$O(n \log n)$做法($100$pts):
fhq平衡树维护序列,代码难度较大(考场上写两个半小时,还是炸到70分),序列翻转,删除,合并都是经典操作。文艺平衡树
Code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4002000;
int n,m,root[N],tot;
namespace treap{
struct fhq_treap{
int ls,rs,col,val,rev;
ll sz,num;
}tr[N];
void init(){
tr[0].ls=0;
tr[0].rs=0;
tr[0].val=0;
tr[0].num=0;
tr[0].sz=0;
tr[0].col=0;
tr[0].rev=0;
}
int newnode(int col,ll num){
++tot;
tr[tot].val=rand()*rand();
tr[tot].col=col,tr[tot].sz=tr[tot].num=num;
tr[tot].ls=tr[tot].rs=0;
tr[tot].rev=0;
return tot;
}
void pushup(int rt){
tr[rt].sz=tr[rt].num;
if(tr[rt].ls){
tr[rt].sz+=tr[tr[rt].ls].sz;
}
if(tr[rt].rs){
tr[rt].sz+=tr[tr[rt].rs].sz;
}
}
void pushdown(int rt){
if(tr[rt].rev){
if(tr[rt].ls){
tr[tr[rt].ls].rev^=1,swap(tr[tr[rt].ls].ls,tr[tr[rt].ls].rs);
}
if(tr[rt].rs){
tr[tr[rt].rs].rev^=1,swap(tr[tr[rt].rs].ls,tr[tr[rt].rs].rs);
}
tr[rt].rev=0;
}
}
int merge(int x,int y){
if((!x)||(!y)){
return x|y;
}
pushdown(x),pushdown(y);
int rt;
if(tr[x].val<tr[y].val){
rt=x;
tr[rt].rs=merge(tr[x].rs,y);
}
else{
rt=y;
tr[rt].ls=merge(x,tr[y].ls);
}
pushup(rt);
return rt;
}
void split(int rt,ll k,int &x,int &y,int &z){
if(!rt){
x=y=z=0;
return;
}
pushdown(rt);
if(tr[tr[rt].rs].sz>=k){
x=rt;
split(tr[rt].rs,k,tr[rt].rs,y,z);
}
else if(tr[tr[rt].rs].sz+tr[rt].num<k){
z=rt;
split(tr[rt].ls,k-tr[tr[rt].rs].sz-tr[rt].num,x,y,tr[rt].ls);
}
else{
x=tr[rt].ls,z=tr[rt].rs;
y=rt;
tr[rt].ls=tr[rt].rs=0;
}
pushup(rt);
}
void pop_nd(int x,int k){
int a,b,c;
split(root[x],k,a,b,c);
root[x]=merge(a,newnode(tr[b].col,tr[c].sz+tr[b].num-k));
cout<<tr[b].col<<endl;
}
}
using namespace treap;
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
freopen("ball.in","r",stdin);
freopen("ball.out","w",stdout);
string s;
cin>>n>>m;
for(int cas=1;cas<=m;++cas){
cin>>s;
int x,y,z;
if(s=="push"){
cin>>x>>y>>z;
root[z]=merge(root[z],newnode(y,x));
}
else if(s=="put"){
cin>>x>>y;
tr[root[x]].rev^=1;
swap(tr[root[x]].ls,tr[root[x]].rs);
root[y]=merge(root[y],root[x]);
root[x]=0;
}else{
cin>>x>>y;
pop_nd(y,x);
}
init();
}
return 0;
}
$O(n)$做法($100$pts):
每个位置用双向链表顺序记录放的球的个数和颜色。
对于询问$1$,往位置$z$的链表上加一个结点$(𝑥,𝑦)$。
对于询问$2$,从位置$z$的链表头取,如果当前需要取$𝒙$个而当前结点拥有的球 $𝑥′<𝑥$个,则整个结点删掉,$𝑥$更新为$𝑥−𝑥′$。如果$𝑥′≥𝑥$,那么输出当前的颜色 $𝑦′$,并且把$𝑥′$更新为$𝑥′−𝑥$。
对于询问$3$,把位置$𝑢$的链表头直接拼接到位置$𝑣$的链表头上,位置$𝑢$的链表尾作为位置$𝑣$新的链表头。
代码难度相对较小。
Code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 301001;
struct node {
int c,num,ch[2];
} buf[maxn];
int n,m,h[maxn],t[maxn],tot;//h:链表头 t:链表尾
int main() {
freopen("ball.in","r",stdin);
freopen("ball.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=0; i<m; i++) {
char op[6];
int x,y,z;
scanf("%s%d%d",op,&x,&y);
if (op[2]=='s') {
scanf("%d",&z);
buf[++tot]=(node){y,x,h[z],0};
if (h[z])
buf[h[z]].ch[0]?(buf[h[z]].ch[1]=tot):(buf[h[z]].ch[0]=tot);//将链表空的一头连接新点
else t[z]=tot;//若链表为空,将该点作为链表尾
h[z]=tot;//更新链表头
}
if (op[2]=='p') {
while (buf[h[y]].num<x) {
x-=buf[h[y]].num;
int lch=buf[h[y]].ch[0]|buf[h[y]].ch[1];//获取链表头的下一个节点
if (lch)buf[lch].ch[0]==h[y]?(buf[lch].ch[0]=0):(buf[lch].ch[1]=0);//删除链表头
h[y]=lch;//更新链表头
}
buf[h[y]].num-=x//减去剩余的
printf("%d\n",buf[h[y]].c);
}
if(op[2]=='t'){
if (!h[x]) continue;//若链表x为空,跳过该操作
if (h[y]) {//完成链表头的连接
buf[h[y]].ch[0]?(buf[h[y]].ch[1]=h[x]):(buf[h[y]].ch[0]=h[x]);//将y链表头空的一头连向x的链表头
buf[h[x]].ch[0]?(buf[h[x]].ch[1]=h[y]):(buf[h[x]].ch[0]=h[y]);//将x链表头空的一头连向y的链表头
} else {
t[y]=h[x]; //若链表y为空,则直接将用x链表头作为y的尾部
}
h[y]=t[x];//更新y链表头
h[x]=t[x]=0;//清空x链表
}
}
return 0;
}
偶数
题面:
牛牛喜欢偶数,他定义一种新的『偶数』为:数字的位数(在十进制下,去掉前导零)为偶数,且数字前一半和后一半完全一致。比如 \(121121\)、\(12341234\) 是『偶数』,而 \(111\)、\(121212\) 不是『偶数』。
对于一个『偶数』,牛牛可以在这个『偶数』后继续添加数字,使得它成为新的『偶数』。比如,\(121121\) 可以在后面添加数字,使之变成 \(1211212112\) 成为新的『偶数』。牛牛总是想添加最少的数字获得新的『偶数』。可以证明添加的方式是唯一的。
对于任何一个『偶数』,牛牛都可以通过上述的方式产生新的『偶数』,这个新的『偶数』继续产生下一个新的『偶数』,直到这个『偶数』的位数超过任意给定的正整数 \(n\) 为止。之后,牛牛会多次询问你,这个最终的『偶数』的第 \(l\) 位到第 \(r\) 位(\(1 \le l \le r \le n\))组成的整数对 \(998244353\) 取模的结果是多少。
题解:
$O(n+q)$做法($40$pts):
假设𝑢是𝑣𝑣,那么新的“偶数”必然是𝑣𝑤𝑣𝑤的形式,其中𝑤是𝑣的一个前缀,且是 𝑣的一个周期。可以证明𝑤应该取𝑣最小的周期。因此使用KMP求𝑣的周期,每 次得到𝑤后,新的𝑣𝑤作为𝑣继续求周期,直到𝑣的长度超过𝑛为止。 询问时预处理$𝑠[𝑖]$为区间$[1,𝑖]$的答案,则$[𝑙,𝑟]$的答案为$𝑠[𝑟]−𝑠[𝑙−1]×10^{r−l+1}$。
$O(\vert S \vert + q \log n)$做法($100$pts):
设$v_0=w$,$v_1=v$,$v_i=v_{i-1}v_{i-2}$,发现最终的字符串一定是$v_{\infty}$的一个前缀,由此我们可以考虑倍增,但在这之前,我们需要证明一个结论
\(w\)是\(v\)的最短周期,且\(len(w)\)不是\(len(v)\)的因子,则\(vw\)的最短的周期是\(v\)
证明:假设$𝑣𝑤$的最短的周期是$𝑥$,$𝑙𝑒𝑛(𝑥)<𝑙𝑒𝑛(𝑣)$。
如果$𝑙𝑒𝑛(𝑥)≡𝑙𝑒𝑛(𝑣)𝑚𝑜𝑑 \ 𝑙𝑒𝑛(𝑤)$,$𝑔𝑐𝑑(𝑙𝑒𝑛(𝑥),𝑙𝑒𝑛(𝑤))$也是𝑣的周期,矛盾。
如果$ 𝑙𝑒𝑛(𝑥)≢𝑙𝑒𝑛(𝑣)𝑚𝑜𝑑 \ 𝑙𝑒𝑛(𝑤)$,$𝑔𝑐𝑑(𝑙𝑒𝑛(𝑤),(𝑙𝑒𝑛(𝑣)−𝑙𝑒𝑛(𝑥))%𝑙𝑒𝑛(𝑤))$是𝑤的周期,从而是𝑣的周期,矛盾。
所以我们只需要预处理$\log n$个$v_i$的长度和值,在处理$[1,l]$和$[1,r]$的值时按长度从大到小的增加即可
Code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=1e5+10;
const int M=100;
#define int long long
char s[N];
int leng,lim,n,q,l,r;
int nxt[N],len[M],f[M],fs[N],base[M];
//快速幂
int qpow(int a,int b){
int res=1;
while(b>0){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res%mod;
}
//KMP
void get_nxt(){
nxt[1]=0;
for(int i=2,j=0;i<=leng;i++){
while(j&&s[j+1]!=s[i])j=nxt[j];
if(s[j+1]==s[i])j++;
nxt[i]=j;
}
}
//倍增
int get_ans(int r){
int sum=0,ans=0;
for(int i=lim;i>=0;i--){
if(sum+len[i]<=r){
sum+=len[i];
ans=(ans*base[i]%mod+f[i])%mod;
}
}
return (ans*qpow(10,r-sum)%mod+fs[r-sum])%mod;//处理剩余部分
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
freopen("code.in","r",stdin);
int t;
cin>>t;
while(t--){
cin>>s+1>>n>>q;
leng=strlen(s+1)/2;
for(int i=1;i<=leng;i++)fs[i]=(fs[i-1]*10LL+s[i]-'0')%mod;//预处理原字符串对应数字
get_nxt();
len[0]=leng-nxt[leng],len[1]=leng;
f[0]=fs[len[0]],f[1]=fs[len[1]];
lim=1;
//倍增
for(int i=2;i<=100;i++){
len[i]=len[i-1]+len[i-2];
f[i]=(f[i-1]*qpow(10,len[i-2])%mod+f[i-2])%mod;
if(len[i]>=n){
lim=i;
break;
}
}
for(int i=0;i<=lim;i++)base[i]=qpow(10,len[i]);//预处理10的幂
for(int i=1;i<=q;i++){
cin>>l>>r;
//截取l-r的数字
cout<<(get_ans(r)-get_ans(l-1)*qpow(10,r-l+1)%mod+mod)%mod<<"\n";
}
}
return 0;
}