凯爹AK了呀
A 事情的相似度
题目大意 : 询问结尾在l到r内的前缀的两两间的最长公共后缀是多少
-
后缀自动机打不熟可不太行
-
建出后缀自动机,给以i结尾的前缀的set里放上i,然后在parent树上进行合并,
-
启发式合并,发现x与fa[x]最长公共子串长度就是len[fa[x]],而且一定是位置相邻的两个作出的贡献
-
启发式合并出来nlogn个点对,最后树状数组维护一下,在skyh的提点下,总复杂度应该是2个log
Code
Show Code
#include <set>
#include <vector>
#include <cstdio>
using namespace std;
const int N = 1e5 + 5;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
char S[N];
set<int> s[N*2];
set<int>::iterator it1, it2;
vector <int> g[N];
vector < pair<int , int> > a[N], q[N];
int n, m, len[N*2], fa[N*2], ch[N*2][2], ans[N], t[N];
void Add(int x, int w) {
for (; x; x -= x & -x) t[x] = max(t[x], w);
}
int Ask(int x, int w = 0) {
for (; x <= n; x += x & -x) w = max(w, t[x]);
return w;
}
int main() {
freopen("A.in", "r", stdin);
freopen("A.out", "w", stdout);
n = read(); m = read();
scanf("%s", S + 1);
int now = 1, trc = 1;
for (int i = 1; i <= n; ++i) {
int x = now, c = S[i] - '0', y;
len[now = ++trc] = len[x] + 1;
g[len[now]].push_back(now);
for (; x && !ch[x][c]; x = fa[x]) ch[x][c] = now;
if (!x) fa[now] = 1;
else if (len[y=ch[x][c]] == len[x] + 1) fa[now] = y;
else {
len[++trc] = len[x] + 1; fa[trc] = fa[y];
ch[trc][0] = ch[y][0]; ch[trc][1] = ch[y][1];
g[len[trc]].push_back(trc);
fa[y] = fa[now] = trc;
for (; x && ch[x][c] == y; x = fa[x]) ch[x][c] = trc;
}
s[now].insert(i);
}
for (int i = n; i >= 1; --i) {
for (int j = 0; j < g[i].size(); ++j) {
int x = g[i][j];
if (s[x].size() > s[fa[x]].size()) swap(s[x], s[fa[x]]);
for (it1 = s[x].begin(); it1 != s[x].end(); ++it1) {
it2 = s[fa[x]].lower_bound(*it1);
if (it2 != s[fa[x]].end()) a[*it2].push_back(make_pair(*it1, len[fa[x]]));
if (it2 != s[fa[x]].begin()) a[*it1].push_back(make_pair(*--it2, len[fa[x]]));
}
for (it1 = s[x].begin(); it1 != s[x].end(); ++it1)
s[fa[x]].insert(*it1);
}
}
for (int i = 1; i <= m; ++i) {
int l = read();
q[read()].push_back(make_pair(l, i));
}
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < a[i].size(); ++j)
Add(a[i][j].first, a[i][j].second);
for (int j = 0; j < q[i].size(); ++j)
ans[q[i][j].second] = Ask(q[i][j].first);
}
for (int i = 1; i <= m; ++i)
printf("%d
", ans[i]);
return 0;
}
B 跳蚤王国的宰相
题目大意 : 让每个点成为到所有节点距离和最小的节点最少需要断开并连上几条边(改变后图还是一颗树0)
-
到所有节点距离和最小的节点其实就是重心,因为如果一个点有子树大小大于n/2,那向那个点移动后到所有节点距离和一定会变小
-
找到原树的重心O,若现在求x的答案,可以发现,x所有子节点的子树,只有O所在的子树是超重的,所以要割这里的边,接到x上
-
以O为根,假设x在O的儿子y的子树中,割y到x这条路径上的点一定是不优的,因为割掉的部分一定大于 n/2,再接到x上也是不行的
-
所以割掉的都是与O相连的边,根据贪心,割掉sz大的子树更优,找到最小的k使得割掉k个子树就小于 n/2 了
-
然后除了O的每个点的答案就是k或k-1了,这个证明听简单,Hiskrrr给我讲了好多遍,但是好像不是很好说,就自己想想吧
-
然后每个点判断一下割掉k-1个行不行,不行就割掉k个,这部分是 (O(n)) 的,但好像没有二分的方法快
Code
Show Code
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e6 + 5;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
struct Edge {
int n, t;
}e[N*2];
int h[N], edc;
void Add(int x, int y) {
e[++edc] = (Edge) {h[x], y}; h[x] = edc;
}
int n, sz[N], rt, mi, a[N], cnt, sum, tmp, k, ans[N];
void Getrt(int x, int fa) {
int mx = 0; sz[x] = 1;
for (int i = h[x], y; i; i = e[i].n) {
if ((y = e[i].t) == fa) continue;
Getrt(y, x); sz[x] += sz[y];
if (sz[y] > mx) mx = sz[y];
}
mx = max(mx, n - sz[x]);
if (mx < mi) mi = mx, rt = x;
}
void Getsz(int x, int fa) {
sz[x] = 1;
for (int i = h[x], y; i; i = e[i].n)
if ((y = e[i].t) != fa)
Getsz(y, x), sz[x] += sz[y];
}
void Dfs(int x, int fa) {
ans[x] = (n - sz[x] - tmp) * 2 > n;
for (int i = h[x], y; i; i = e[i].n)
if ((y = e[i].t) != fa) Dfs(y, x);
}
int main() {
freopen("B.in", "r", stdin);
freopen("B.out", "w", stdout);
mi = n = read();
for (int i = 1; i < n; ++i) {
int x = read(), y = read();
Add(x, y); Add(y, x);
}
Getrt(1, 0); Getsz(rt, 0);
for (int i = h[rt]; i; i = e[i].n)
a[++cnt] = sz[e[i].t];
sort(a + 1, a + cnt + 1);
for (int i = cnt; i >= 1; --i) {
sum += a[i]; k++;
if (sum * 2 >= n) break;
}
for (int i = h[rt]; i; i = e[i].n)
tmp = sum - max(sz[e[i].t], a[cnt-k+1]), Dfs(e[i].t, rt);
k--; ans[rt] = -k;
for (int i = 1; i <= n; ++i)
printf("%d
", k + ans[i]);
return 0;
}
C 蛐蛐国的修墙方案
题目大意 : 给一个排列,需要生成一个合法的括号序列,使得左括号的位置i向p[i]连边后,图中所有点的度数都是1
-
因为坐标也是个排列,一个数在坐标和p中只会各出现一次,所以i和p[i]两个位置一定不一样
-
i会对p[i]造成影响,这样建出约束关系,一定是若干个环(p是排列),而且都是偶环(奇环会冲突)
-
长度为2的环是直接可以确定,位置靠前的是左括号,靠后的是右括号,这是因为合法的括号序列要满足任意位置左括号的数量要不少于右括号的数量,这样如果前面放右括号成立,那前面放左括号也成立
-
剩下的环枚举第一个是什么括号就行,复杂度(O(2^{frac{n}{4}}n))
Code
Show Code
#include <cstdio>
using namespace std;
const int N = 105;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
bool v[N];
char c[N];
int n, t[N], tot, p[N];
bool Judge() {
int tp = 0;
for (int i = 1; i <= n; ++i) {
if (c[i] == '(') tp++;
else if (tp) tp--;
else return 0;
}
return !tp;
}
int main() {
freopen("C.in", "r", stdin);
freopen("C.out", "w", stdout);
n = read();
for (int i = 1; i <= n; ++i) t[i] = read();
for (int i = 1; i <= n; ++i) {
if (v[i]) continue;
int cnt = 1; v[i] = 1;
for (int x = t[i]; x != i; x = t[x])
v[x] = 1, cnt++;
if (cnt == 2) c[i] = '(', c[t[i]] = ')';
else p[++tot] = i;
}
for (int s = 1 << tot; s >= 1; --s) {
for (int i = 1; i <= tot; ++i) {
c[p[i]] = '(' + ((s >> i - 1) & 1);
for (int x = p[i]; t[x] != p[i]; x = t[x])
c[t[x]] = c[x] ^ 1;
}
if (Judge()) break;
}
printf("%s
", c + 1);
return 0;
}