2021/7/15c++
水,略ide
建个 Trie 树跑一波,不过这么小的数据范围貌似不用优化
不难发现就是将 \(N\) 个数分红 \(3\) 堆,前两堆大小相同,使得第三堆最小。ui
定义状态 \(f[i][j][k]\) 表示前面 \(i\) 个数,第 \(1\) 堆大小为 \(j\),第 \(2\) 堆大小为 \(k\) ,是否可行,直接转移,貌似能够 bitset 优化作到 \(\mathcal{O}(\dfrac{N(\sum c)^2}{w})\)。spa
比较套路的作法,对后两个维度做差,定义状态 \(f[i][j]\) 表示前 \(i\) 个数,前两堆差为 \(j\) 时,第 \(1\) 堆能够达到的最大重量时多少。字符串
直接转移 \(\mathcal{O}(N\sum c)\) 已经能够经过。get
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 100005 using namespace std; int f[N << 1], n, g[N << 1]; inline void ck(int &x,int y){if(y > x)x = y;} int main(){ scanf("%d", &n); int sum = 0; memset(f, 0xcf, sizeof(f)); int bas = N - 5; f[bas] = 0; rep(i, 1, n){ memset(g, 0xcf, sizeof(g)); int x;scanf("%d", &x); rep(j, -sum, sum) ck(g[j + bas], f[j + bas]), ck(g[j + x + bas], f[j + bas] + x), ck(g[j - x + bas], f[j + bas]); sum += x;rep(j, -sum, sum)f[j + bas] = g[j + bas]; } printf("%d\n", sum - f[bas]); return 0; }
感受是个错题啊,像 \(\frac{1}{3}\) 这样无限小数怎么处理精度啊。it
考虑逆向思惟,咱们要将最终集合的数一一删去。class
显然最小的数只能被不大于它的数删去,那么咱们枚举整个数。循环
而这个数必定是最小的数除以一个正整数倍率,咱们从小到大枚举倍率,同时判断在这个倍率下,区间\([A,B]\) 中是否有删除了没有的元素。
因为 \(K\) 很小,因此判断和删除都是 \(\mathcal{O}(K)\) 级别的,因此复杂度是 \(\mathcal{O}(K^2)\) ?感受挺玄学的。
另外赛时发现 luogu 的 spj 全是锅,直接 Hack spj 就能过。
挺有意思的题。
刚开始看很是没有思路就先开 T6 了。
先想到多是 DAG 求最长路,发现不大可作。
观察一下这个 \(LCS \ge Len - 1\) 的条件。两个串长度不相等,必定一个是另外一个的前缀且长度相差 \(1\) ,不然必定两个串最后觉得必定不相等。
因此长度相等的串且前缀相等大概是两两连边,而后不等的串连边是惟一的。感受也不是很可作。
可是把这个连边放到 Trie 树上乱搞,发现这就是一个走 \(Trie\) 树的过程。咱们要找的是是一条路径,使得路径长度加上与路径直接相连的边的长度之和最大。
直接树上 DP 便可,时间复杂度 \(\mathcal{O}(N + \sum|S|)\),须要注意的是 Trie 的空间,大概得用 vector 维护以时间换空间。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 3000005 using namespace std; int n, idx, ed[N]; vector<pair<int,int>>ch[N]; char s[N]; int get(int x,int y){ for(auto z:ch[x])if(z.first == y)return z.second; return 0; } int f[N], ans; void dfs(int x){ f[x] = ed[x]; int mx = 0, nxt = 0, sz = 0; for(auto y:ch[x]){ dfs(y.second); if(ed[y.second]){ sz++, nxt = max(nxt, f[y.second]); if(nxt > mx)swap(nxt, mx); } }ans = max(ans, nxt + mx + f[x] + max(0, sz - 2)); f[x] += mx + max(0, sz - 1); } int main(){ scanf("%d", &n); rep(i, 1, n){ scanf("%s", s + 1); int m = strlen(s + 1), cur = 0; pre(j, m, 1){ int now = get(cur, s[j] - 'a'); if(!now) ch[cur].push_back(make_pair(s[j] - 'a', ++idx)), cur = idx; else cur = now; } ed[cur] = 1; //cout<<"ww "<<cur<<endl; } dfs(0);printf("%d\n", ans); return 0; }
求几率输出准确值还不用取模,显然是直接枚举全部可能(
显然这个无限矩阵就是以给定的子矩阵为元不断循环,若是咱们固定一个方向,那么以 \((i,j)\)为起点的串,和以 \((i+an, j+bm),\ a,b\in \mathcal{Z}\) 为起点的串必定相同。
因此咱们只用枚举起点在 \((i, j), i\in[0, n - 1], j\in[0,m -1]\) 的串便可,而后就是求这个串的哈希值。
这个字符串开始和结束的几个单独拎出来,中间不断循环。开始和结束的哈希值能够预处理,中间的是个等比数列,能够直接通项公式爆算。
细节比较多。注意斜的,横的,竖的的循环节可能都不相同,斜的循环节和 \(n,m\) 的最大公约数有关,不过咱们能够直接对整个子矩阵遍历一遍避免过多讨论。
另外建议双哈希,赛时随机了一个\(10^9\) 左右的质数 \(934567889\) 被卡了 \(40\) 分,而后又随了两个质数仍是挂了,查了半天还觉得是哪里挂了,后来才发现原来是卡了哈希,估计出题人把 \(10^9\) 左右的模数都卡了。
后来找到 \(bas = 229, P = 10^9 + 97\) 没有卡(
时间复杂度 \(\mathcal{O}(NM\log)\) ,实现较丑还排了序。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 505 #define P 1000000097 #define bas 229 using namespace std; int n, m, k, v[N][N], mat[N][N], idx, f[N][N], pw[N * N]; vector<int>c[N]; char s[N][N], cp[N][N]; void ins(int x,int y){ v[x][y] = ++idx;mat[x][y] = 0; c[idx].push_back(s[x][y]); while(true){ x = (x + 1) % n; y = (y + 1) % m; if(v[x][y])break ; v[x][y] = idx, mat[x][y] = c[idx].size(), c[idx].push_back(s[x][y]); } int w = c[idx].size(); //cout<<"init " <<w<<endl; rep(i, 1, w - 1)c[idx][i] = (1LL * c[idx][i - 1] * bas + c[idx][i]) % P; } void init(){ rep(i, 0, n - 1){ f[i][0] = s[i][0]; rep(j, 1, m - 1) f[i][j] = (1LL * f[i][j - 1] * bas + s[i][j]) % P; } rep(i, 0, n - 1)rep(j, 0, m - 1)if(!v[i][j])ins(i, j); } vector<int>w; int Pow(int x,int y){ int now = 1; for(;y;y >>= 1, x = 1LL * x * x % P)if(y & 1)now = 1LL * now * x % P; return now; } inline int g(int x,int y){ if(x == 1)return y + 1; return 1LL * (Pow(x, y + 1) - 1) * Pow(x - 1, P - 2) % P; } int calc(int x,int bs,int cnt){return 1LL * x * g(bs, cnt - 1) % P;} int row(int x,int y){ int res = k, pr = 0, cur = 0; if(y)pr = f[x][y - 1]; if(m - y >= res)return (f[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P; cur = (f[x][m - 1] - 1LL * pr * pw[m - y] % P + P) % P, res -= m - y; if(res <= m)return (1LL * pw[res] * cur + f[x][res - 1]) % P; int cc = res / m; res %= m; cur = 1LL * cur * Pow(bas, cc * m) % P; cur = (cur + calc(f[x][m - 1], pw[m], cc)) % P; if(!res)return cur; return (1LL * pw[res] * cur + f[x][res - 1]) % P; } int col(int x,int y){ int t = v[x][y]; y = mat[x][y], x = t; int w = c[x].size(), res = k, pr = 0, cur = 0; if(y)pr = c[x][y - 1]; if(w - y >= res)return (c[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P; cur = (c[x][w - 1] - 1LL * pr * pw[w - y] % P + P) % P, res -= w - y; if(res <= w)return (1LL * pw[res] * cur + c[x][res - 1]) % P; int cc = res / w; res %= w; cur = 1LL * cur * Pow(bas, cc * w) % P; cur = (cur + calc(c[x][w - 1], pw[w], cc)) % P; if(!res)return cur; return (1LL * pw[res] * cur + c[x][res - 1]) % P; } void rotate(){ rep(i, 0, n - 1)rep(j, 0, m - 1)cp[j][n - 1 - i] = s[i][j]; swap(n, m); rep(i, 0, n - 1)rep(j, 0, m - 1)s[i][j] = cp[i][j]; } long long gcd(long long x,long long y){return y ? gcd(y, x % y) : x;} signed main(){ scanf("%d%d%d", &n, &m, &k); pw[0] = 1;rep(i, 1, n * m)pw[i] = 1LL * pw[i - 1] * bas % P; rep(i, 0, n - 1)scanf("%s", s[i]); rep(op, 0, 3){ memset(v, 0, sizeof(v)); rep(i, 1, idx)c[i].clear(); idx = 0;init(); rep(i, 0, n - 1)rep(j, 0, m - 1) w.push_back(row(i, j)), w.push_back(col(i, j)); rotate(); } sort(w.begin(), w.end());int sum = 0, pr = ~0;long long ans = 0; for(int x : w){ if(pr != x)ans += 1LL * sum * sum, sum = 0, pr = x; sum++; }ans += 1LL * sum * sum;//cout<<endl; long long fac = 1LL * w.size() * w.size(); long long cur = gcd(ans, fac); printf("%lld/%lld\n", ans / cur, fac / cur); return 0; }
2021/7/15
水,略
建个 Trie 树跑一波,不过这么小的数据范围貌似不用
不难发现就是将 \(N\) 个数分红 \(3\) 堆,前两堆大小相同,使得第三堆最小。
定义状态 \(f[i][j][k]\) 表示前面 \(i\) 个数,第 \(1\) 堆大小为 \(j\),第 \(2\) 堆大小为 \(k\) ,是否可行,直接转移,貌似能够 bitset 优化作到 \(\mathcal{O}(\dfrac{N(\sum c)^2}{w})\)。
比较套路的作法,对后两个维度做差,定义状态 \(f[i][j]\) 表示前 \(i\) 个数,前两堆差为 \(j\) 时,第 \(1\) 堆能够达到的最大重量时多少。
直接转移 \(\mathcal{O}(N\sum c)\) 已经能够经过。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 100005 using namespace std; int f[N << 1], n, g[N << 1]; inline void ck(int &x,int y){if(y > x)x = y;} int main(){ scanf("%d", &n); int sum = 0; memset(f, 0xcf, sizeof(f)); int bas = N - 5; f[bas] = 0; rep(i, 1, n){ memset(g, 0xcf, sizeof(g)); int x;scanf("%d", &x); rep(j, -sum, sum) ck(g[j + bas], f[j + bas]), ck(g[j + x + bas], f[j + bas] + x), ck(g[j - x + bas], f[j + bas]); sum += x;rep(j, -sum, sum)f[j + bas] = g[j + bas]; } printf("%d\n", sum - f[bas]); return 0; }
感受是个错题啊,像 \(\frac{1}{3}\) 这样无限小数怎么处理精度啊。
考虑逆向思惟,咱们要将最终集合的数一一删去。
显然最小的数只能被不大于它的数删去,那么咱们枚举整个数。
而这个数必定是最小的数除以一个正整数倍率,咱们从小到大枚举倍率,同时判断在这个倍率下,区间\([A,B]\) 中是否有删除了没有的元素。
因为 \(K\) 很小,因此判断和删除都是 \(\mathcal{O}(K)\) 级别的,因此复杂度是 \(\mathcal{O}(K^2)\) ?感受挺玄学的。
另外赛时发现 luogu 的 spj 全是锅,直接 Hack spj 就能过。
挺有意思的题。
刚开始看很是没有思路就先开 T6 了。
先想到多是 DAG 求最长路,发现不大可作。
观察一下这个 \(LCS \ge Len - 1\) 的条件。两个串长度不相等,必定一个是另外一个的前缀且长度相差 \(1\) ,不然必定两个串最后觉得必定不相等。
因此长度相等的串且前缀相等大概是两两连边,而后不等的串连边是惟一的。感受也不是很可作。
可是把这个连边放到 Trie 树上乱搞,发现这就是一个走 \(Trie\) 树的过程。咱们要找的是是一条路径,使得路径长度加上与路径直接相连的边的长度之和最大。
直接树上 DP 便可,时间复杂度 \(\mathcal{O}(N + \sum|S|)\),须要注意的是 Trie 的空间,大概得用 vector 维护以时间换空间。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 3000005 using namespace std; int n, idx, ed[N]; vector<pair<int,int>>ch[N]; char s[N]; int get(int x,int y){ for(auto z:ch[x])if(z.first == y)return z.second; return 0; } int f[N], ans; void dfs(int x){ f[x] = ed[x]; int mx = 0, nxt = 0, sz = 0; for(auto y:ch[x]){ dfs(y.second); if(ed[y.second]){ sz++, nxt = max(nxt, f[y.second]); if(nxt > mx)swap(nxt, mx); } }ans = max(ans, nxt + mx + f[x] + max(0, sz - 2)); f[x] += mx + max(0, sz - 1); } int main(){ scanf("%d", &n); rep(i, 1, n){ scanf("%s", s + 1); int m = strlen(s + 1), cur = 0; pre(j, m, 1){ int now = get(cur, s[j] - 'a'); if(!now) ch[cur].push_back(make_pair(s[j] - 'a', ++idx)), cur = idx; else cur = now; } ed[cur] = 1; //cout<<"ww "<<cur<<endl; } dfs(0);printf("%d\n", ans); return 0; }
求几率输出准确值还不用取模,显然是直接枚举全部可能(
显然这个无限矩阵就是以给定的子矩阵为元不断循环,若是咱们固定一个方向,那么以 \((i,j)\)为起点的串,和以 \((i+an, j+bm),\ a,b\in \mathcal{Z}\) 为起点的串必定相同。
因此咱们只用枚举起点在 \((i, j), i\in[0, n - 1], j\in[0,m -1]\) 的串便可,而后就是求这个串的哈希值。
这个字符串开始和结束的几个单独拎出来,中间不断循环。开始和结束的哈希值能够预处理,中间的是个等比数列,能够直接通项公式爆算。
细节比较多。注意斜的,横的,竖的的循环节可能都不相同,斜的循环节和 \(n,m\) 的最大公约数有关,不过咱们能够直接对整个子矩阵遍历一遍避免过多讨论。
另外建议双哈希,赛时随机了一个\(10^9\) 左右的质数 \(934567889\) 被卡了 \(40\) 分,而后又随了两个质数仍是挂了,查了半天还觉得是哪里挂了,后来才发现原来是卡了哈希,估计出题人把 \(10^9\) 左右的模数都卡了。
后来找到 \(bas = 229, P = 10^9 + 97\) 没有卡(
时间复杂度 \(\mathcal{O}(NM\log)\) ,实现较丑还排了序。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 505 #define P 1000000097 #define bas 229 using namespace std; int n, m, k, v[N][N], mat[N][N], idx, f[N][N], pw[N * N]; vector<int>c[N]; char s[N][N], cp[N][N]; void ins(int x,int y){ v[x][y] = ++idx;mat[x][y] = 0; c[idx].push_back(s[x][y]); while(true){ x = (x + 1) % n; y = (y + 1) % m; if(v[x][y])break ; v[x][y] = idx, mat[x][y] = c[idx].size(), c[idx].push_back(s[x][y]); } int w = c[idx].size(); //cout<<"init " <<w<<endl; rep(i, 1, w - 1)c[idx][i] = (1LL * c[idx][i - 1] * bas + c[idx][i]) % P; } void init(){ rep(i, 0, n - 1){ f[i][0] = s[i][0]; rep(j, 1, m - 1) f[i][j] = (1LL * f[i][j - 1] * bas + s[i][j]) % P; } rep(i, 0, n - 1)rep(j, 0, m - 1)if(!v[i][j])ins(i, j); } vector<int>w; int Pow(int x,int y){ int now = 1; for(;y;y >>= 1, x = 1LL * x * x % P)if(y & 1)now = 1LL * now * x % P; return now; } inline int g(int x,int y){ if(x == 1)return y + 1; return 1LL * (Pow(x, y + 1) - 1) * Pow(x - 1, P - 2) % P; } int calc(int x,int bs,int cnt){return 1LL * x * g(bs, cnt - 1) % P;} int row(int x,int y){ int res = k, pr = 0, cur = 0; if(y)pr = f[x][y - 1]; if(m - y >= res)return (f[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P; cur = (f[x][m - 1] - 1LL * pr * pw[m - y] % P + P) % P, res -= m - y; if(res <= m)return (1LL * pw[res] * cur + f[x][res - 1]) % P; int cc = res / m; res %= m; cur = 1LL * cur * Pow(bas, cc * m) % P; cur = (cur + calc(f[x][m - 1], pw[m], cc)) % P; if(!res)return cur; return (1LL * pw[res] * cur + f[x][res - 1]) % P; } int col(int x,int y){ int t = v[x][y]; y = mat[x][y], x = t; int w = c[x].size(), res = k, pr = 0, cur = 0; if(y)pr = c[x][y - 1]; if(w - y >= res)return (c[x][y + res - 1] - 1LL * pr * pw[res] % P + P) % P; cur = (c[x][w - 1] - 1LL * pr * pw[w - y] % P + P) % P, res -= w - y; if(res <= w)return (1LL * pw[res] * cur + c[x][res - 1]) % P; int cc = res / w; res %= w; cur = 1LL * cur * Pow(bas, cc * w) % P; cur = (cur + calc(c[x][w - 1], pw[w], cc)) % P; if(!res)return cur; return (1LL * pw[res] * cur + c[x][res - 1]) % P; } void rotate(){ rep(i, 0, n - 1)rep(j, 0, m - 1)cp[j][n - 1 - i] = s[i][j]; swap(n, m); rep(i, 0, n - 1)rep(j, 0, m - 1)s[i][j] = cp[i][j]; } long long gcd(long long x,long long y){return y ? gcd(y, x % y) : x;} signed main(){ scanf("%d%d%d", &n, &m, &k); pw[0] = 1;rep(i, 1, n * m)pw[i] = 1LL * pw[i - 1] * bas % P; rep(i, 0, n - 1)scanf("%s", s[i]); rep(op, 0, 3){ memset(v, 0, sizeof(v)); rep(i, 1, idx)c[i].clear(); idx = 0;init(); rep(i, 0, n - 1)rep(j, 0, m - 1) w.push_back(row(i, j)), w.push_back(col(i, j)); rotate(); } sort(w.begin(), w.end());int sum = 0, pr = ~0;long long ans = 0; for(int x : w){ if(pr != x)ans += 1LL * sum * sum, sum = 0, pr = x; sum++; }ans += 1LL * sum * sum;//cout<<endl; long long fac = 1LL * w.size() * w.size(); long long cur = gcd(ans, fac); printf("%lld/%lld\n", ans / cur, fac / cur); return 0; }