# 12602 - OuQ String > author: tangerine1202, ryona, atsushi ###### tags: `csst` --- ## Brief 有以下三種解法 :::info ## Keyword List 英文 | 中文 | 補充或連結 --------|--------|-------- ::: ## Solution 1 ### 變數、函式定義 - `ll len[k]`:S~k~的字串長度。 - `void solve(ll l, ll r, int k)` - `ls`、`rs`:將查詢區間從對齊S~k~開頭,改為對齊關注部分開頭,方便比對。(s意指shift) - 功能:輸出S~k~[l~r]字串 ### 想法: - 先觀察S~k~ = "O" + S~k-1~ + "u" + S~k-1~ + "Q",k<=50。 S~50~的長度約等於2^50^(≒10^15^),而char陣列至多開到10^9^左右,故無法以陣列模擬。 - 考慮以遞迴處理。將S~k~拆成"O"、S~k-1~、"u"、S~k-1~、"Q",五個部分。 ### `void solve(ll l, ll r, int k)` - Basic step - 當k==1時,直接輸出"OuQ"中被查詢的部分。 - Recursion step - S~k-1~部分:如果查詢區間涵蓋S~k-1~,則維護左右界後遞迴S~k-1~。 - "O"、"u"、"Q"部分:如在查詢區間內,則直接輸出。 ```c #include <stdio.h> #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) typedef long long ll; const ll inf = 1e18; ll len[60]; void solve(ll l, ll r, ll k) { if (k == 1) { for (int i = l; i <= r; i++) printf("%c", "OuQ"[i]); return; } // O ll ls = l, rs = r; if (ls <= 0 && 0 <= rs) printf("O"); // S_k-1 ls = l - 1; rs = r - 1; if (0 <= rs && ls <= len[k - 1] - 1) solve( max(ls, 0), min(rs, len[k - 1] - 1), k - 1); // u ls = l - 1 - len[k - 1]; rs = r - 1 - len[k - 1]; if (ls <= 0 && 0 <= rs) printf("u"); // S_k-1 ls = l - 1 - len[k - 1] - 1; rs = r - 1 - len[k - 1] - 1; if (0 <= rs && ls <= len[k - 1] - 1) solve( max(ls, 0), min(rs, len[k - 1] - 1), k - 1); // Q ls = l - 1 - len[k - 1] - 1 - len[k - 1]; rs = r - 1 - len[k - 1] - 1 - len[k - 1]; if (ls <= 0 && 0 <= rs) printf("Q"); } int main() { int n = 1; len[1] = 3; // while( 2*len[n]+3 <= inf ) // 但因 2*len[n]+3 可能會溢位,故移項以防止溢位問題 while ((inf - 3) / 2 > len[n]) { n++; len[n] = len[n - 1] * 2 + 3; } ll l, r, k; while (scanf("%lld%lld%lld", &k, &l, &r) != EOF) { solve(l, r, k); printf("\n"); } return 0; } // By tangerine1202 ``` ## Solution 2 概念與解法一差不多,但可能有比較白話 由以下兩個步驟推廣。 -----處理k很小的時候: 依據題目,可以設計出 'O' + (k-1) + 'u' + (k-1) + 'Q' 的遞迴式,而且不必優化。 -----對於k比較大的想法: tmp是指k級字串的第tmp個字元 ( 字串是想像的 ) 。 我們需要判斷tmp是否在左界和右界之間。 如果是,就輸出。 先一層一層進入遞迴,若是tmp + 下一層的字串長度 ( len[k] )仍然比左界還要小,就直接讓tmp+=len[k],並且return回到上一層。 ```c #include <stdio.h> int k; long long l, r, tmp, len[53], r1; void rec(int k){ if( tmp+len[k] <= l ){ //優化時間複雜度 tmp+=len[k]; return ; } else if(tmp <= r){ //如果tmp>r,直接return if(tmp >= l && tmp <= r) printf("O"); tmp++; if(k!=1 && tmp <= r) rec(k-1); if(tmp >= l && tmp <= r) printf("u"); tmp++; if(k!=1 && tmp <= r) rec(k-1); if(tmp >= l && tmp <= r) printf("Q"); tmp++; }//tmp在範圍內就print,k不等於1以及tmp在範圍內就遞迴 else return ; } //反正長度都固定,用陣列記下來可以省時間(dp) long long lenk(int k){ if(len[k]) return len[k]; if(k==1) return len[1] = 3; return len[k] = 3 + 2*lenk(k-1); } int main(){ lenk(50); while( scanf("%d %lld %lld", &k, &l, &r)!=EOF ){ tmp = 0; rec(k); printf("\n"); } return 0; } // By ryona ``` ## Solution 3 ```c #include <stdio.h> long long length[53],l , r;; int k; char s1[5] = "OuQ"; char askingchar(int linex, long long chdex){ // linex 是等同於k // chdex 等同於k行的幾個字(0開始) if(linex == 1) // 遞迴到1 return s1[chdex]; if(chdex == 0) // 剛好要第一個字,一定是O return 'O'; else if (chdex == length[linex]-1) // 剛好要最後一個字,一定是Q return 'Q'; else if (chdex == length[linex-1]+1) // 剛好要中間字,一定是u return 'u'; else { if(chdex < length[linex-1]+1) return askingchar(linex-1,chdex-1); // 將chdex 換算成上一行的第幾個字 else return askingchar(linex-1,chdex-length[linex-1]-2); // 同理,換算 } } int main(){ length[1] = 3; for(int i=2;i<=53;i++){ length[i] = 2 * length[i-1] + 3; } while(~scanf("%d %lld %lld", &k, &l, &r)){ for(int i=l;i<=r;i++){ printf("%c",askingchar(k,i)); } printf("\n"); } return 0; } // By atsushi ``` ## Solution 4 ```c #include <stdio.h> long long k, l, r, len; char search(long long n, long long len, long long k); int main() { while (scanf("%lld %lld %lld", &k, &l, &r) == 3) { len = 3 * (1ll << k) - 3; // count the length for (long long i = l; i <= r; i++) printf("%c", search(i, len, k)); printf("\n"); } } char search(long long n, long long len, long long k) { if (n < k) return 'O'; // 前k項必為'O' if (n == (len-1) / 2) return 'u'; // 中間項必為'u' if (n > len-1-k) return 'Q'; // 後k項必為'Q' // 將字串拆成 O, sk-1, u, sk-1, Q 五個部分 len = (len-3) / 2; if (n > len) return search(n-len-2, len, k-1); // 第2段sk-1 else return search(n-1, len, k-1); // 第1段sk-1 } // By Utin ``` ## Reference