A.重新排列 #尺取法
题意
给定字符串,你需要将该字符串中的某个子串重新排列,使得该子串变为"puleyaknoi"。现要你求出最短的这类子串的长度。
分析
因为子串要求连续性,“连续性”和“多个满足条件中选择最优的”,可以联想到尺取法。
怎么判断是否满足条件?由于子串能够重新排列,我们只需要统计相应区间中模式串对应字符的数量即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <string> #include <cstring> #include <cstdio> #include <iostream> #include <stack> #include <queue> #include <map> #include <vector> #include <deque> #include <cmath> #include <algorithm> #include <unordered_map> using namespace std; typedef long long ll; string t = {"puleyaknoi"}, tmp; int vis[300]; int q, cnt = 0; void Cal(char cur){ for (int j = 0; j < t.length(); j++){ if(cur == t[j]){ if(vis[cur] == 0) cnt++; vis[cur]++; break; } } } int main(){ scanf("%d", &q); while(q--){ cin >> tmp; int len = tmp.length(), hi = -1; int minlen = 0x3f3f3f3f; memset(vis, 0, sizeof(vis)); cnt = 0; for (int lo = 0; lo <= len - t.length(); lo++){ while(hi < len && cnt < t.length()){ hi++; Cal(tmp[hi]); } if(hi >= len) break; if (cnt >= (int)t.length() && hi - lo + 1 < minlen) minlen = hi - lo + 1; if(vis[tmp[lo]] >= 1){ vis[tmp[lo]]--; if(vis[tmp[lo]] == 0) cnt--; } } if(minlen < 0x3f3f3f3f) printf("%d\n", minlen); else printf("-1\n"); } return 0; }
|
B.拼凑 #线性DP
题意
给定字符串,你需要从该字符串中找出一子序列,使得该子序列为"puleyaknoi"。现要你求出最短的这类子串的长度。
分析
因为子序列是原串中一部分字符(不一定是连续的)按原有次序排列而得的序列。碰到“子序列”相关问题,往往从DP方向考虑。
定义dp[i][j],i表示主串的下标i,j表示模式串下标j,当j==模式串长度时,dp数组记录最短子序列长度。其转移方程在代码中有注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <string> #include <cstring> #include <cstdio> #include <iostream> #include <stack> #include <queue> #include <map> #include <vector> #include <deque> #include <cmath> #include <algorithm> #include <unordered_map> using namespace std; typedef long long ll; const int MAXN = 1e5 + 5; string s = {"puleyaknoi"}, tmp; int dp[MAXN][12], q; int main(){ scanf("%d", &q); while(q--){ cin >> tmp; int n = tmp.length(), m = s.length(); memset(dp, 0x3f, sizeof(dp)); for (int i = 1; i <= n; i++) dp[i][0] = 0; for (int i = 1; i <= n; i++){ for (int j = 1; j <= min(i, m); j++){ dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1); if(tmp[i - 1] == s[j - 1]) dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + 1); } } int ans = 0x3f3f3f3f; for (int i = m; i <= n; i++) ans = min(ans, dp[i][m]); printf("%d\n", ans < 0x3f3f3f3f ? ans : -1); } }
|
C.Mu函数 #质因数分解 #模拟 #打表观察性质
题意
定义函数mu(x),满足:
mu(x)=⎩⎨⎧1(−1)a0,x=1,x无平方因数且x=∏i=1api,pi为质数),x含大于1的平方数因数
设函数f(x)=x+mu(x),给定n,k,求f(n)的k(k≤1018)次迭代
分析
这题Wa了十多发,看到好多题解需要莫比乌斯反演(?),没学过。
参考了@为神的思路,我们要解决几个问题:
- 如何判断x是否存在平方数因数?直接枚举即可。
- 如何对x质因数分解?先用线性筛,记录每个数是否为质数的同时,给每一个合数存下相应的最小质因子。接下来对x不断地用其最小质因子去除,直到商为质数即可统计出原来的x有多少个质因子。
- 如何处理1018的k?如此数据规模,暗示我们要从中找出算法提前终止的条件与有规律性的结果。
- 如果x满足mu(x)的条件三,说明接下来的增量都为0,f(x)始终不变,直接退出;
- 如果x满足mu(x)的条件二,实际上就是对x自减或者自增。我们通过小范围打表发现,到达一定的k时,mu(x)会在−1和+1之间周期变化,也就说上一增量和下一增量之和就是0,由此我们通过周期规律,预先知道未来的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <string> #include <cstring> #include <cstdio> #include <iostream> #include <stack> #include <queue> #include <map> #include <vector> #include <deque> #include <algorithm> using namespace std; typedef long long ll; const int MAXN = 1e7 + 5; int q, n, prim[MAXN], notPrim[MAXN]; ll k; void getPrime(){ int cnt = 0; for(ll i = 2; i <= (ll)1e7+3; i++){ if(notPrim[i] == 0) prim[++cnt] = i; for(int j = 1; i * (ll)prim[j] <= (ll)1e7+3 && j <= cnt; j++){ notPrim[i * prim[j]] = prim[j]; if(i % prim[j] == 0) break; } } } int res; bool Judge(int x){ res = 0; for(int i = 2; i * i <= x; i++) if(x % (i * i) == 0) return false; while(notPrim[x] > 0){ x = x / notPrim[x]; res++; } if(x != 1) res++; return true; } int main(){ scanf("%d", &q); getPrime(); while(q--){ scanf("%d%lld", &n, &k); int last = 0, diff = 0; while(k--){ last = diff; if(Judge(n)){ diff = 1 - 2 * (res % 2); n += diff; if(diff + last == 0){ if(k % 2 != 0) n += last; break; } } else break; } printf("%d\n", n); } }
|
D.数树 #树的性质 #STL
题意
现有三种操作:1. 添加一条边;2. 切断一条边;3.询问现有多少个规模不为1的树。
操作中有可能连出重边,或者切断根本不存在的边,可以忽略。
分析
由于每一棵树满足"边数+1=节点数",我们可以用总边数-节点数,便能求出森林中树的数量。但注意,这些树都是由度数不为1的节点组成的。另外,由于会出现重边,或者删除不存在的边,我们需要用哈希表去记录边的数量。又因为节点编号达到108,需要用到map和pair来维护与记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <string> #include <cstring> #include <cstdio> #include <iostream> #include <stack> #include <queue> #include <map> #include <vector> #include <deque> #include <cmath> #include <algorithm> #include <unordered_map> #include <set> using namespace std; typedef long long ll; const int MAXN = 2e5 + 5; map< pair<int, int>, int> Evis; map<int, int> deg; int ans = 0, n, cnt, tot; int main() { scanf("%d", &n); while (n--) { int opt, u, v; scanf("%d", &opt); if (opt == 1) { scanf("%d%d", &u, &v); if(Evis.count({u, v})) continue; Evis[{u, v}] = Evis[{v, u}] = 1; cnt++; deg[u]++; deg[v]++; if(deg[u] == 1) tot++; if(deg[v] == 1) tot++; } else if (opt == 2) { scanf("%d%d", &u, &v); if(!Evis.count({u, v})) continue; Evis.erase({u, v}); Evis.erase({v, u}); cnt--; deg[u]--; deg[v]--; if(deg[u] == 0) tot--; if(deg[v] == 0) tot--; } else if (opt == 3) { printf("%d\n", tot - cnt); } } }
|