训练赛地址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"); } }