【总结】COCI2016-2017#4
2021/7/15
T1 :Bridž
水,略
T2 :Kartomat
建个 Trie 树跑一波,不过这么小的数据范围貌似不用
T3:Kas
不难发现就是将 \(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;
}
T4:Rekonstruiraj
感觉是个错题啊,像 \(\frac{1}{3}\) 这样无限小数怎么处理精度啊。
考虑逆向思维,我们要将最终集合的数一一删去。
显然最小的数只能被不大于它的数删去,那么我们枚举整个数。
而这个数一定是最小的数除以一个正整数倍率,我们从小到大枚举倍率,同时判断在这个倍率下,区间\([A,B]\) 中是否有删除了没有的元素。
由于 \(K\) 很小,所以判断和删除都是 \(\mathcal{O}(K)\) 级别的,所以复杂度是 \(\mathcal{O}(K^2)\) ?感觉挺玄学的。
另外赛时发现 luogu 的 spj 全是锅,直接 Hack spj 就能过。
T5:Rima
挺有意思的题。
刚开始看非常没有思路就先开 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;
}
T6:Osmosmjerka
求概率输出准确值还不用取模,显然是直接枚举所有可能(
显然这个无限矩阵就是以给定的子矩阵为元不断循环,如果我们固定一个方向,那么以 \((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;
}