訓練賽地址c++
題目連接
⭐⭐算法
題意:
給出下述兩種操做數組
如今給出\(a\)與\(b\),問使得1經過上述操做變成\(\frac{a}{2^b}\)的最小長度指令序列(假設整數位的右邊就是對應的小數位)
保證\(a\)是奇數函數
解析:佈局
觀察原公式,無非是把\(a\)化成2進制,再將他向右邊移動\(n\)位。因爲數據條件的限制,這個數必定是一個小數。這樣就把問題轉化成如何在小數部分湊出\(a\)所對應的二進制序列了。
觀察操做指令,若是想要得到一個連續爲1的序列學習
例如得到0.111,能夠先將1右移3位變成0.001,再經過1-x,變成所須要的0.111spa
能夠發現這個過程當中最後一位是保持不變的1,而以前產生的連續0由於1-x產生了反轉變成了連續1。在這樣的基礎上若是再次左移,就能夠得到連續的0,如此交替往復,咱們彷佛能夠獲得任何想要的二進制序列,也就達到了要求.net
對於最後一個出現的0(1),也就是第一位來講,它必定是1,因此當將這一位移動到小數部分時(也就是循環結束時),必需要進行1操做code
最後輸出剩餘的0便可排序
補充:當\(a\)爲奇數時,最後一位始終是1,而無論哪一種指令操做,所構成的小數最後一位也是1,兩者是相對應的,所以第一步永遠是0
特殊狀況:若是隻移動1位時,也就是0.1的狀況,這種時候先後位置不同,可是\(1-x=x\),不必進行多餘的1操做
總結:每次判斷轉化成的二進制數據,每讀取一位就進行0操做(右移),若是當前位與上一位不相等1操做(反轉,結束0或1開始錄入異位連續01序列),對移動1次進行特判,最後輸出剩餘0
#include<cstdio> using namespace std; long long a, b; int main(void) { scanf("%lld%lld", &a, &b); int c = 1; a >>= 1; //記錄上一次 bool last = true; printf("0"); while (a) { if ((a & 1) != last) { //特判 if (c != 1) printf("1"); } last = (a & 1); ++c; printf("0"); a >>= 1; } //特判 if (c != 1) printf("1"); for (int i = c; i < b; ++i) printf("0"); }
題目連接
⭐
題意:
給出五根木棍,問能組成多少個不一樣的三角形
解析:
枚舉三根木棍的組合狀況,兩邊之和大於第三邊斷定便可,再將這三個木棍從小到大排列用\(map\)記錄便可
錯誤之處:
用\(dfs\)進行枚舉時,對存儲數據的數組直接進行排序,從而影響了搜索時的存儲順序
一直WA呀,QAQ
#include<cstdio> #include<algorithm> #include<map> using namespace std; int ret[3], ans, a[5]; map<int, map<int, map<int, bool> > > m; void dfs(int cnt, int x) { if (x == 5) { ans += cnt == 3 && ret[0] + ret[1] > ret[2] && ret[0] + ret[2] > ret[1] && ret[1] + ret[2] > ret[0] && !m[ret[0]][ret[1]][ret[2]]; return; } dfs(cnt, x + 1); if (cnt < 3) { ret[cnt] = a[x]; dfs(cnt + 1, x + 1); } } int main(void) { for (int i = 0; i < 5; ++i) scanf("%d", &a[i]); //提早sort 保證枚舉出來的數列必定是非嚴格單調遞增的 sort(a, a + 5); dfs(0, 0); printf("%d", ans); }
題目連接
⭐⭐⭐
題意:
由‘A’,‘B’,‘C’三種類型字母組成的長度爲n的字符串,且對於連續的 k 個字符,A的數量 + B的數量等於C的數量,求方案數
解析:
經過所給公式可知,在\(k\)長度的區間中\(|C|=|D|=\frac{k}{2}\),那麼對於每種排列布局,他們的\(1\)部分D的數量都是相同的,有\(\left\lfloor\frac{n}{k}\right\rfloor\times\frac{k}{2}\)個,即對應\(2^{\left\lfloor\frac{n}{k}\right\rfloor\times\frac{k}{2}}\)
4. 對於\(2\)部分,能夠再次將區間\(k\)分爲長度爲\(n\% k\)的前半部分和剩餘的後半部分,對於前半部分,能夠挑選出\(i\)個位置給D存放,對應種類數爲\(C_{n\%k}^i\),同時後半部分要將剩下的\(\frac{k}{2}-i\)個D挑選完畢,即\(C_{k-n\%k}^{\frac{k}{2}-i}\),同時要注意越界溢出狀況,即
再根據加法原理,能夠獲得後半部分對應的公式
總結:
#include<cstdio> #include<algorithm> typedef long long LL; using namespace std; /*===========================================*/ LL n; int k; const LL mod = 1e9 + 7; LL q_pow(LL x, LL n) { LL ans = 1; while (n) { if (n & 1) ans = ans * x % mod; x = x * x % mod; n >>= 1; } return ans; } long long jc[100100];//階層數組 long long inv[100100];//逆元數組 long long bas[100100]; void exgcd(long long a, long long b, long long& x, long long& y) { if (b) x = 1, y = 0; else { exgcd(b, a % b, y, x); y -= a / b * x; } } long long pow(long long x, int n) { long long result = 1; while (n) { if (n & 1) result = result * x % mod; n >>= 1; x = x * x % mod; } return result; } void pre() { jc[0] = inv[0] = 1; jc[1] = 1; bas[0] = 1; bas[1] = 2; for (int i = 2; i <= 100000; ++i) jc[i] = jc[i - 1] * i % mod, bas[i] = bas[i - 1] * 2 % mod; inv[100000] = q_pow(jc[100000], mod - 2); for (int i = 99999; i >= 0; --i) inv[i] = inv[i + 1] * ((LL)i + 1LL) % mod; return; } long long C(int n, int m) { return jc[n] * inv[m] % mod * inv[n - m] % mod; } int main(void) { pre(); int T; LL ans; scanf("%d", &T); while (T--) { scanf("%lld%d", &n, &k); if (k & 1) { printf("0\n"); continue; } ans = 0; int tmp = n % k, end = min(tmp, k / 2); for (int i = max(0LL, n % k - k / 2); i <= end; ++i) ans = (ans + C(k - tmp, k / 2 - i) * C(tmp, i) % mod * bas[i] % mod) % mod; ans = ans * q_pow(2, n / k * (k / 2)) % mod; printf("%lld\n", ans); } }
題目連接 B
⭐⭐⭐⭐⭐
題意:
如今有兩個字符串\(s\)和\(t\),\(q\)次查詢,每次查詢給出一個 \(l,r\) 求\(s[l] \sim s[r]\)中有多少個\(t\)的子串
解析:
++le
,用\(d\)數組記錄從當前位置開始,向左看最長公共串的起始位置,而且再維護一個關於\(le\)前綴和數組\(sum\)總結:維護\(t\)的SAM,用SAM檢測\(s\),記錄每一個位置向左看最長的公共子串的起始位置以及對應長度的前綴和
吐槽:學了兩天後綴自動機,覺着轉換函數是對某個子串的拓展操做,且保證這個拓展是後綴子串,而前綴連接就至關於對前綴子串前半部分的刪除操做,一加一減讓SAM能夠獲取關於子串的信息,而不只僅是後綴串。感受這個算法真挺難的,並且應用還不太會,如今只是看題解會了這道題,後續繼續學習相關算法叭……
#include<bits/stdc++.h> using namespace std; /*===========================================*/ int d[75005], sum[75005]; char s1[75005], s2[75005]; struct SAM { int size, last; vector<int> len, link; vector< vector<int>> to; void init(int strLen = 0, int chSize = 0) { strLen *= 2; size = last = 1; len.assign(strLen, 0); link.assign(strLen, 0); to.assign(strLen, vector<int>(chSize, 0)); link[1] = len[1] = 0; } void extend(int c) { int p, cur = ++size; len[cur] = len[last] + 1; //狀況1 for (p = last; p && !to[p][c]; p = link[p]) to[p][c] = cur; if (!p) link[cur] = 1; else { int q = to[p][c]; //A類 if (len[q] == len[p] + 1) link[cur] = q; else //B類 { int clone = ++size; len[clone] = len[p] + 1; link[clone] = link[q], to[clone] = to[q]; while (p && to[p][c] == q) to[p][c] = clone, p = link[p]; link[cur] = link[q] = clone; } } last = cur; } void solve() { int le = 0, p = 1, c; for (int i = 1; s1[i]; ++i) { c = s1[i] - 'a'; while (p != 1 && !to[p][c]) p = link[p], le = len[p]; if (to[p][c]) ++le, p = to[p][c]; d[i] = i - le + 1; sum[i] = sum[i - 1] + le; } } }sol; int main() { freopen("curse.in", "r", stdin); int T; int q; scanf("%d", &T); for (int cas = 1; cas <= T; ++cas) { scanf("%s%s", s1 + 1, s2 + 1); sol.init(strlen(s2 + 1), 26); for (int i = 1; s2[i]; ++i) sol.extend(s2[i] - 'a'); sol.solve(); scanf("%d", &q); printf("Case %d:\n", cas); while (q--) { int l, r, L, R; scanf("%d%d", &l, &r); L = l, R = r; while (L < R) { int mid = L + (R - L) / 2; if (d[mid] < l) L = mid + 1; else R = mid; } printf("%lld\n", 1LL * sum[r] - sum[L - 1] + 1LL * (L - l + 1) * (L - l) / 2); } } }
題目連接 H
⭐⭐⭐
題意:
給定區間\([L,R]\),求出知足下列條件的區間內最大的數
解析:
最樸素的思想必然是直接從L到R進行拆分,但在\(10^{13}\)的數據下必定會TLE,考慮分治作法
因爲要求\(gcd\ne 1\),因此能夠考慮一方爲素數的狀況,若是考慮past爲素數,則修改pre的狀況下,改動範圍太大,會出現錯誤,所以考慮pre是否爲素數的狀況
對於每一個數當前數\(n\)
#include<cstdio> #include<algorithm> typedef long long LL; using namespace std; LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } int pre, past; void div(LL x) { int ans = 0; LL t = x; while (t) t /= 10, ++ans; int mol = 1; ans = (ans + 1) / 2; while (ans--) mol *= 10; pre = x / mol; past = x % mol; } const int maxn = 1e7; int cnt; int prime[664579]; bool vis[maxn + 1]; void euler() { vis[0] = vis[1] = true; for (LL i = 2; i <= maxn; ++i) { if (!vis[i]) prime[cnt++] = i; for (int j = 0; j < cnt && i * prime[j] <= maxn; ++j) { vis[i * prime[j]] = true; if (i % prime[j] == 0) break; } } } int main(void) { freopen("halfnice.in", "r", stdin); euler(); int T; LL l, r; bool ok; scanf("%d", &T); for (int cas = 1; cas <= T; ++cas) { ok = false; scanf("%lld%lld", &l, &r); printf("Case %d: ", cas); while (r >= l) { div(r); if (!vis[pre]) { if (pre <= past) { ok = true; printf("%lld\n", r - past % pre); break; } else r -= past + 1; } else { if (gcd(pre, past) != 1 && past) { ok = true; printf("%lld\n", r); break; } else --r; } } if (!ok) printf("impossible\n"); } }