NOIP模擬賽(10.17):語言,色球,斐波,偶數

LISOP發表於2024-10-19

語言

題面:

牛妹正在學習一種新的語言,在這種語言裡,單詞只有形容詞(\(\texttt{A}\)),名詞(\(\texttt{N}\))和動詞(\(\texttt{V}\))三種詞性。但是一個單詞可以對應多種詞性。

一個名詞性片語(\(\texttt{NP}\))可以由一個名詞(\(\texttt{N}\)),或者一個形容詞修飾一個子名詞性片語(\(\texttt{A} + \texttt{NP}_1\)),或者兩個子名詞性片語(\(\texttt{NP}_1 + \texttt{NP}_2\))組成。即:

\[\texttt{NP} ::= \texttt{N} \mid \texttt{A} + \texttt{NP}_1 \mid \texttt{NP}_1 + \texttt{NP}_2 \]

一個句子(\(\texttt{S}\))必須由一個名詞性片語(\(\texttt{NP}_1\))加一個動詞(\(\texttt{V}\))再加一個名詞性片語(\(\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\) 種操作中的一個:

  1. push x y z,表示把 \(x\) 個顏色為 \(y\) 的彩色小球放到第 \(z\) 個桶的最上面;
  2. pop x z,表示把最上面的 \(x\) 個小球從第 \(z\) 個桶內拿出來;
  3. 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;
}

結束咯(T3先鴿著)