回文自动机原理与实现
概述 回文自动机(简称PAM,又称回文树,Palindromic Tree)是一种用于处理 …
May all the beauty be blessed.
题目地址:洛谷:【P4142】洞穴遇险 – 洛谷
前方有一片沼泽地.
方便地, 我们用一个n × n 的网格图来描述它, 每一个格子代表着沼泽地的一小片区域. 其中(1, 1) 代表网格图的左上角, (n, n) 代表网格图的右下角. 若用X 表示行数, Y 表示列数, 那么X + Y 为奇数的格子有一个危险度VX, Y , X + Y 为偶数的格子的危险度为0.
为了保障人们的安全, 你有m 个长相怪异的大石头, 你可以选一些石头放在网格图上的某些格子上, 石头可以看成一个‘L’ 形的块, 并且占三个格子, 它通过旋转有四种方式供放置, 仅会使得在拐角处的那个格子危险度减为0.
网格图中还有k 个位置是“禁止位置”, 石头的任何部位都不能位于这些格子上, 且这些位置的危险度一定为0.
现在你需要知道放置一些石头后最小的危险度之和是多少. (石头可以不放完)
输入格式:
从文件marshland.in 中读入数据。
第一行三个整数n; m; k.
接下来n 行每行n 个整数, 表示每个格子的危险度, 保证X + Y 为偶数的格子和禁止位置的格子的危险度为0.
接下来k 行每行2 个整数X; Y , 表示禁止位置的坐标, 注意有可能会出现重复的禁止位置.
输出格式:
输出到文件marshland.out 中
输出一行一个整数代表最小的危险度之和.
输入样例#1:
3 3 1 0 1 0 2 0 1 0 1 0 1 3
输出样例#1:
3
输入样例#2:
3 3 4 0 2 0 0 0 4 0 3 0 1 3 2 1 2 2 3 1
输出样例#2:
9
对于10%的数据,满足n ≤ 4,
对于30%的数据,满足n ≤ 10,
对于100%的数据,满足n ≤ 50,
对于所有的数据,满足0 ≤ m ≤ n^2/3 ; 0 ≤ k ≤ n2; 0 ≤ VX;Y ≤ 10^6.
雅礼集训Day 5 T1,网络流好题。
我们想用对每个有危险度的格子确定一种安排方案,使得有与它有公共边的两个相邻非对位格子被覆盖或者选择不覆盖这个格子,而且数据范围也很网络流,所以可以考虑网络流。
但是无论是用有危险度的格子去匹配方案,还是用方案去匹配有危险度的的格子,都存在一个问题:流量控制在相邻的格子上,然而存在两个需要流量控制的点,不得不把匹配的流量加到2,但是存在可能无法跑满流的情况,这是无法解决的。
我们考虑,假如对于这样一个情况
---- | 1| ------- |DG| 2| -------
其中,DG是有危险度的格子,我们让流量从1流入,流经DG后从2流出,就无需考虑流量2无法满流的情况了。对于石头数m的限制,直接进行m次增广就可以解决这个问题了。需要注意的是,这里要求的是最大费用可行流,需要实时取min。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
typedef long long LL;
const int MAXN = 5005, INF = 1e9;
struct Edge {
int to, cap, cost, nxt;
} gra[MAXN << 4];
int head[MAXN], tot;
inline void addedge(int u, int v, int cap, int cost) {
gra[tot] = (Edge) {v, cap, cost, head[u]}; head[u] = tot++;
gra[tot] = (Edge) {u, 0, -cost, head[v]}; head[v] = tot++;
}
int f[MAXN], pre[MAXN], pree[MAXN];
LL dis[MAXN];
bool inque[MAXN];
std::queue<int> que;
inline bool spfa(int s, int t) {
memset(f, 0, sizeof(f));
memset(dis, 0x3f, sizeof(dis));
que.push(s); f[s] = INF; dis[s] = 0; inque[s] = true;
while(!que.empty()) {
int u = que.front(); que.pop();
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to; inque[v] = false;
if(gra[i].cap && dis[v] > dis[u] + gra[i].cost) {
dis[v] = dis[u] + gra[i].cost;
f[v] = std::min(f[u], gra[i].cap);
pre[v] = u; pree[v] = i;
if(!inque[v]) {
inque[v] = true; que.push(v);
}
}
}
}
return f[t];
}
LL flow, cost;
LL ans;
inline void mcmf(int s, int t) {
while(spfa(s, t)) {
flow += f[t]; cost += 1ll * f[t] * dis[t];
for(int i = t; i != s; i = pre[i]) {
gra[pree[i]].cap -= f[t]; gra[pree[i] ^ 1].cap += f[t];
}
ans = std::min(cost, ans);
}
}
int n, m, k, a[55][55], S, S1, T;
bool forbid[55][55];
inline int num(int x, int y) {
return (x - 1) * n + y;
}
int main() {
freopen("marshland.in", "r", stdin);
freopen("marshland.out", "w", stdout);
memset(head, -1, sizeof(head));
scanf("%d%d%d", &n, &m, &k);
S = 5000; S1 = 5001; T = 5002;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
scanf("%d", &a[i][j]);
cost += a[i][j];
}
}
ans = cost;
for(int i = 1, x, y; i <= k; i++) {
scanf("%d%d", &x, &y);
forbid[x][y] = true;
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(((i + j) & 1) && !forbid[i][j]) {
addedge(num(i, j), n * n + num(i, j), 1, -a[i][j]);
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(!((i + j) & 1) && !forbid[i][j]) {
if(i & 1) {
addedge(S1, num(i, j), 1, 0);
if(i - 1 >= 1) addedge(num(i, j), num(i - 1, j), 1, 0);
if(i + 1 <= n) addedge(num(i, j), num(i + 1, j), 1, 0);
if(j - 1 >= 1) addedge(num(i, j), num(i, j - 1), 1, 0);
if(j + 1 <= n) addedge(num(i, j), num(i, j + 1), 1, 0);
} else {
addedge(num(i, j), T, 1, 0);
if(i - 1 >= 1) addedge(n * n + num(i - 1, j), num(i, j), 1, 0);
if(i + 1 <= n) addedge(n * n + num(i + 1, j), num(i, j), 1, 0);
if(j - 1 >= 1) addedge(n * n + num(i, j - 1), num(i, j), 1, 0);
if(j + 1 <= n) addedge(n * n + num(i, j + 1), num(i, j), 1, 0);
}
}
}
}
addedge(S, S1, m, 0);
mcmf(S, T);
printf("%lld", ans);
return 0;
}
题目地址:洛谷:【P4292】[WC2010]重建计划 – 洛谷、BZOJ:Problem 1758. — [Wc2010]重建计划
给你一棵树,求长度在[L, U]范围内的树链的边权平均值的最大值。
输入格式:
第一行包含一个正整数N,表示X国的城市个数。
第二行包含两个正整数L、U,表示政府要求的第一期重建方案中修建道路数的上下限。
接下来的N-1行描述重建小组的原有方案,每行三个正整数ai, bi, vi,分别表示道路(ai, bi),其价值为vi。其中城市由1…N标号。
输出格式:
仅包含一行,为一个实数AvgValue,即最大平均价值。
小数点后保留三位。
输入样例#1:
4 2 3 1 2 1 1 3 2 1 4 3
输出样例#1:
2.500
新方案中选择路径(3, 1), (1, 4)可以得到的平均价值为2.5,为最大平均价值。
对于20%的数据,N ≤ 5 000;
另有30%的数据,N ≤ 100 000, 原有方案恰好为一条路径(链);
对于100%的数据,N ≤ 100 000, 1 ≤ L ≤ U ≤ N-1, vi ≤ 10^6。
整个树找树链可以考虑使用点分治。
对于每个重心,我们考虑二分答案,验证的时候找两条子树到重心的路径拼起来长度在范围内的,或者一条路径长度就在范围内的,在处理边权和的时候在每个边权上减掉答案,则只需要判断边权和是否不小于0即可。
由于对于以前找到过的每一个深度的链,只需要存下边权和最大值,就可以直接拿来判断了。而枚举每个深度去遍历合法区间,会造成复杂度退化为$O(n^2)$。我们考虑单调队列优化这一过程,从浅到深枚举当前遍历子树的每个节点,对应的在以前找到过的深度区间可以确定为[L-d, R-d],由于遍历的顺序单调,单调队列可以倒着从深到浅维护最大值来判定,遍历节点时使用BFS,这样节点的遍历顺序就是从浅到深单调的了,这样判定的复杂度是$O(n)$的了。
单调队列的复杂度其实应该是$O(之前找到过的最大深度)$的,因此,我们可以对每一棵子树的深度进行排序,从浅的子树开始遍历,可以优化时间,不过我采用了快排,排序复杂度会多一个$\log n$。
另外,本题有一定的卡常数成分,有以下几个卡常的地方可以作为参考:
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
inline int max(int a, int b) {
return a > b ? a : b;
}
inline double max(double a, double b) {
return a > b ? a : b;
}
const int MAXN = 100005;
const double EPS = 1e-4;
int n, L, U;
double ans;
struct Edge {
int to, w, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;
inline void addedge(int u, int v, int w) {
gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, w, head[v]}; head[v] = tot++;
}
bool vis[MAXN];
int siz[MAXN], rt, rtsiz;
inline void findrt(int u, int fa, int tot) {
siz[u] = 1; int msiz = 0;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(vis[v] || v == fa) continue;
findrt(v, u, tot);
siz[u] += siz[v];
msiz = max(msiz, siz[v]);
}
msiz = max(msiz, tot - siz[u]);
if(msiz < rtsiz) {
rt = u; rtsiz = msiz;
}
}
int fa[MAXN], dep[MAXN], que[MAXN], deq[MAXN];
double dis[MAXN];
int tdep[MAXN], subt[MAXN], subw[MAXN], stot;
inline bool cmp(int a, int b) {
return tdep[a] < tdep[b];
}
inline int dfs(int u, int fa, int dep) {
int mxdep = dep;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(vis[v] || v == fa) continue;
mxdep = max(mxdep, dfs(v, u, dep + 1));
}
return mxdep;
}
double mx[MAXN];
inline bool check(int u, double mid) {
int mxdep = 0;
stot = 0;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
subt[++stot] = v;
subw[v] = gra[i].w;
tdep[v] = dfs(v, u, 1);
}
std::sort(subt + 1, subt + stot + 1, cmp);
for(register int i = 1; i <= stot; i++) {
int v = subt[i];
if(vis[v]) continue;
int hd = 0, tl = 1; fa[v] = u; dep[v] = 1; dis[v] = subw[v] - mid; que[0] = v;
while(hd < tl) {
int uu = que[hd++];
for(register int j = head[uu]; ~j; j = gra[j].nxt) {
int vv = gra[j].to;
if(vis[vv] || vv == fa[uu]) continue;
fa[vv] = uu;
dep[vv] = dep[uu] + 1;
dis[vv] = dis[uu] + gra[j].w - mid;
que[tl++] = vv;
}
}
int l = 1, r = 0, now = mxdep;
for(register int j = 0; j < tl; j++) {
int p = que[j];
while(dep[p] + now >= L && now >= 0) {
while(l <= r && mx[deq[r]] < mx[now]) r--;
deq[++r] = now; now--;
}
while(l <= r && dep[p] + deq[l] > U) l++;
if(l <= r && dis[p] + mx[deq[l]] + EPS >= 0) return true;
}
for(register int j = mxdep + 1; j <= dep[que[tl - 1]]; j++) {
mx[j] = -1e9;
}
for(register int j = 0; j < tl; j++) {
int p = que[j];
mx[dep[p]] = max(mx[dep[p]], dis[p]);
}
mxdep = max(mxdep, dep[que[tl - 1]]);
}
return false;
}
int lim;
inline void divide(int u) {
register double l = ans, r = lim, mid;
while(r - l > EPS) {
mid = (l + r) / 2;
if(check(u, mid)) l = mid; else r = mid;
}
ans = l;
vis[u] = true;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(vis[v] || siz[v] < L) continue;
rt = 0; rtsiz = n; findrt(v, u, siz[v]);
divide(rt);
}
}
int main() {
memset(head, -1, sizeof(head));
n = readint(); L = readint(); U = readint();
for(register int i = 1, a, b, c; i < n; i++) {
a = readint(); b = readint(); c = readint();
lim = max(lim, c);
addedge(a, b, c);
}
rt = 0; rtsiz = n; findrt(1, 0, n);
divide(rt);
printf("%.3lf", ans);
return 0;
}
题目地址:BZOJ:Problem 3091. — 城市旅行
输入格式:
输出格式:
输入样例#1:
4 5 1 3 2 5 1 2 1 3 2 4 4 2 4 1 2 4 2 3 4 3 1 4 1 4 1 4
输出样例#1:
16/3 6/1
对于所有数据满足 1<=N<=50,000 1<=M<=50,000 1<=Ai<=10^6 1<=D<=100 1<=U,V<=N
参考资料:BZOJ 3091 城市旅行 Link-Cut-Tree – CSDN博客
其实本题是[HAOI2012]高速公路一题扩展到树上的做法。由于树需要动态维护,自然会想到使用LCT。
我们考虑一条树链上的期望应该如何计算,可以求出树链上每个点对期望的分子的贡献,对于a_i,它的贡献是i(n-i+1),即我们要求的分子是\sum_{i=1}^n a_i \cdot i \cdot (n-i+1),我们得想办法在LCT上把这个值维护出来。
于是我们就想到了,能不能先求出子树对应树链的答案,然后再怎么样拼起来呢?
我们观察一下结果吧,假设左子树树链长为4,右子树长为2
想求的东西 1*7*a1 + 2*6*a2 + 3*5*a3 + 4*4*a4 + 5*3*a5 + 6*2*a6 + 7*1*a7 两个子树的答案加起来 1*4*a1 + 2*3*a2 + 3*2*a3 + 4*1*a4 + + 1*2*a6 + 2*1*a7 上式减下式 1*3*a1 + 2*3*a2 + 3*3*a3 + 4*3*a4 + 5*3*a5 + 5*2*a6 + 5*1*a7
a5是树根处的元素可以单独算,3其实是右子树大小+1,5则是左子树大小+1,我们考虑同时维护一个lsum = \sum_{i=1}^n a_i \cdot i和一个rsum = \sum_{i=1}^n a_i \cdot (n-i+1),就可以借助这些来求整条树链的答案了,即
ans = ans_l + ans_r + (siz_l+1)(siz_r+1)a_{root} + (siz_r+1)(lsum_l) + (siz_l+1)(rsum_r)
而lsum、rsum的合并很简单,只需要把子树元素和乘上另一侧子树的个数+1就好,元素和可以顺便维护一下。
此外,本题还要求一个树链加法标记,如何应用这个加法标记到我们维护的信息上就会是问题。lsum、rsum需要加上add \cdot \sum_{i=1}^n i,比较好办,而期望的分子需要加上add \cdot \sum_{i=1}^n i(n-i+1),这个并不好办,不过我们有数学方法得到这个求和的公式,最后得出的结果是\sum_{i=1}^n i(n-i+1) = \frac{n(n+1)(n+2)}{6}。
万事大吉了,剩下的就是把上面的东西码进去。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for (; !isdigit(c); c = fgc()) if (c == '-') neg = -1;
for (; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 50005;
struct Node {
int ch[2], fa; LL siz, val, lsum, rsum, sum, exp;
bool rev; LL add;
} tr[MAXN];
inline bool isleft(int p) {
return tr[tr[p].fa].ch[0] == p;
}
inline bool isroot(int p) {
return tr[tr[p].fa].ch[0] != p && tr[tr[p].fa].ch[1] != p;
}
inline void update(int p) {
tr[p].siz = tr[tr[p].ch[0]].siz + tr[tr[p].ch[1]].siz + 1;
tr[p].sum = tr[tr[p].ch[0]].sum + tr[tr[p].ch[1]].sum + tr[p].val;
tr[p].lsum = tr[tr[p].ch[0]].lsum + tr[p].val * (tr[tr[p].ch[0]].siz + 1)
+ tr[tr[p].ch[1]].lsum + tr[tr[p].ch[1]].sum * (tr[tr[p].ch[0]].siz + 1);
tr[p].rsum = tr[tr[p].ch[0]].rsum + tr[tr[p].ch[0]].sum * (tr[tr[p].ch[1]].siz + 1)
+ tr[p].val * (tr[tr[p].ch[1]].siz + 1) + tr[tr[p].ch[1]].rsum;
tr[p].exp = tr[tr[p].ch[0]].exp + tr[tr[p].ch[1]].exp
+ tr[tr[p].ch[0]].lsum * (tr[tr[p].ch[1]].siz + 1)
+ tr[tr[p].ch[1]].rsum * (tr[tr[p].ch[0]].siz + 1)
+ (tr[tr[p].ch[0]].siz + 1) * (tr[tr[p].ch[1]].siz + 1) * tr[p].val;
}
inline void reverse(int p) {
std::swap(tr[p].ch[0], tr[p].ch[1]);
std::swap(tr[p].lsum, tr[p].rsum);
tr[p].rev ^= 1;
}
inline void add(int p, LL v) {
tr[p].val += v;
tr[p].sum += v * tr[p].siz;
tr[p].lsum += v * tr[p].siz * (tr[p].siz + 1) / 2;
tr[p].rsum += v * tr[p].siz * (tr[p].siz + 1) / 2;
tr[p].exp += v * tr[p].siz * (tr[p].siz + 1) * (tr[p].siz + 2) / 6;
tr[p].add += v;
}
inline void pushdown(int p) {
if(tr[p].rev) {
if(tr[p].ch[0]) reverse(tr[p].ch[0]);
if(tr[p].ch[1]) reverse(tr[p].ch[1]);
tr[p].rev ^= 1;
}
if(tr[p].add) {
if(tr[p].ch[0]) add(tr[p].ch[0], tr[p].add);
if(tr[p].ch[1]) add(tr[p].ch[1], tr[p].add);
tr[p].add = 0;
}
}
int sta[MAXN], stop;
inline void pushto(int p) {
stop = 0;
while(!isroot(p)) {
sta[stop++] = p; p = tr[p].fa;
}
sta[stop++] = p;
while(stop) {
pushdown(sta[--stop]);
}
}
inline void rotate(int p) {
bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
tr[p].fa = ffa; if(!isroot(fa)) tr[ffa].ch[!isleft(fa)] = p;
tr[fa].ch[t] = tr[p].ch[!t]; tr[tr[fa].ch[t]].fa = fa;
tr[p].ch[!t] = fa; tr[fa].fa = p;
update(fa);
}
inline void splay(int p) {
pushto(p);
for(int fa = tr[p].fa; !isroot(p); rotate(p), fa = tr[p].fa) {
if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
}
update(p);
}
inline void access(int p) {
for(int q = 0; p; q = p, p = tr[p].fa) {
splay(p); tr[p].ch[1] = q; update(p);
}
}
inline void makert(int p) {
access(p); splay(p); reverse(p);
}
inline int findrt(int p) {
access(p); splay(p);
while(tr[p].ch[0]) p = tr[p].ch[0];
return p;
}
inline void split(int u, int v) {
makert(u); access(v); splay(v);
}
inline void link(int u, int v) {
if(findrt(u) == findrt(v)) return;
split(u, v); tr[u].fa = v;
}
inline void cut(int u, int v) {
split(u, v);
if(tr[v].ch[0] != u || tr[u].ch[1]) return;
tr[u].fa = tr[v].ch[0] = 0; update(v);
}
int n, m;
std::vector<int> gra[MAXN];
inline void dfs(int u, int fa) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(v == fa) continue;
tr[v].fa = u;
dfs(v, u);
}
}
inline LL gcd(LL x, LL y) {
if(!y) return x;
return gcd(y, x % y);
}
int main() {
n = readint(); m = readint();
for(int i = 1; i <= n; i++) {
tr[i].val = readint(); update(i);
}
for(int i = 1, a, b; i < n; i++) {
a = readint(); b = readint();
gra[a].push_back(b); gra[b].push_back(a);
}
dfs(1, 0);
int op, u, v; LL d;
while(m--) {
op = readint(); u = readint(); v = readint();
if(op == 1) {
cut(u, v);
} else if(op == 2) {
link(u, v);
} else if(op == 3) {
d = readint(); if(findrt(u) != findrt(v)) continue;
split(u, v); add(v, d);
} else {
if(findrt(u) != findrt(v)) {
puts("-1"); continue;
}
split(u, v);
LL up = tr[v].exp, down = (tr[v].siz + 1) * tr[v].siz / 2, g = gcd(up, down);
printf("%lld/%lld\n", up / g, down / g);
}
}
return 0;
}