[树链剖分][最小割]
orz zcg
给出一棵树,n<=10^6,有一些询问,每次求(u, v)的LCA
树剖卡内存也可以这么写啊。。
然而这样的做法会被卡,如果一条链每一个甩出一个子节点就会被卡。
还有sdoi2016那道游戏,贪心更新标记即可。
#include <cstdio> #define maxn 1000001 int n, fa[maxn], top[maxn]; int main(){ freopen("squirrel.in", "r", stdin); freopen("squirrel.out", "w", stdout); int test, x; scanf("%d%d", &n, &test); top[1] = -1; for(int i = 2; i <= n; i ++){ scanf("%d", &x); fa[i] = x; if(top[x] < 0)top[i] = top[x], top[x] = -top[x]; else top[i] = -i; } for(int i = 1; i <= n; i ++) if(top[i] < 0) top[i] = -top[i]; int u, v; for(int i = 1; i <= test; i ++){ scanf("%d%d", &u, &v); while(top[u] != top[v]){ if(fa[top[u]] < fa[top[v]]) u ^= v ^= u ^= v; u = fa[top[u]]; } printf("%d\n", u < v ? u : v); } return 0; }
2. 百步穿杨
(archerycpp/c/pas)
【问题描述】
Star 是一个神箭手,不过并不是他自己射箭——在一块N * M 的方格空地上有些地方摆有箭塔,其它还有地方有箭靶。每个箭靶有不同的分值,而箭塔的方向为上下左右其中的一种,Star 通过控制箭塔选定目标,然后一起发射击中这些箭靶获取其分数来展现自己的“射箭”技术。现在你需要计算在给定的空地上,Star 最多能获得多大的分数。
相关的限制如下:
1、每个箭塔原则上可以选择所指向方向上任意一个箭靶,当然也可以选择不攻击;
2、为保证正常运行,每个箭塔的攻击范围中不会有其它箭塔;
3、同样为确保正常攻击,所有箭塔的攻击轨迹在地面的投影不能在任意格
子相交。
【输入】
第一行两个整数N、M;
接下来N 行每行M 个字符,表示每个格子的状态:
1、“.”表示空地;
2、“A”、“V”、“<”、“>”分别表示朝向上下左右的箭塔;
3、“1”~“9”分别表示得分为1 到9 的箭靶。
【输出】
输出一行一个整数,表示最大得分。
【输入输出样例】
archery.in archery.out
3 4
.9V.
>..7
.A1.
10
【数据范围】
对于30%的数据,N, M ≤ 5;
对于100%的数据,N, M ≤ 50。
一眼最小割(然而并没有什么卵用)
看错题了啊QAQ。。任意一个。。
这个建图好神奇。。
根据数据范围,我们想办法把原问题转化为最小割。我们把每个格子i 拆成两个点i、i',分别表示这个格子被横向或纵向的轨迹穿过。不妨设i 与S 相连表示纵向穿过、i'与T 相连表示横向穿过。由于求最小割,我们首先贪心出最大收益——每个箭塔均取它发射方向上的最大值(忽略其它一切限制)。我们要“割”的正是不合法情况,考察有哪些限制:
①一个点不能同时取纵向与横向;
②箭塔所在格不能取和它本身不同的方向;
③顺着一个箭塔的轨迹往下走,若轨迹在去向某些格子处停止,则要付出该箭塔最大值减当前收益的代价。
以上限制均转化为最小割的建边,然后跑网络流即可。
O(Maxflow(N2, N3))。
这样的图竟然不到0.1ms就过了
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <vector> #include <queue> #define maxn 51 using namespace std; char a[maxn][maxn]; int n, m, S, T; //---------------------------------------------------------------------// struct Edge{ int to, next, w, dis; Edge(int v = 0, int nxt = 0, int w = 0): to(v), next(nxt), w(w){} }edge[5000010]; int h[100010], cnt = 1; void add(int u, int v, int w){ edge[++ cnt] = Edge(v, h[u], w); h[u] = cnt; edge[++ cnt] = Edge(u, h[v], 0);h[v] = cnt; } queue<int> Q; int d[100010]; const int inf = 0x7fffffff / 2; bool BFS(){ Q.push(S); for(int i = 1; i <= T; i ++) d[i] = -1; d[S] = 0; while(!Q.empty()){ int u = Q.front(); Q.pop(); for(int i = h[u]; i; i = edge[i].next){ if(edge[i].w == 0)continue; int v = edge[i].to; if(d[v] == -1)d[v] = d[u] + 1, Q.push(v); } }return d[T] != -1; } int DFS(int x, int a){ if(a == 0 || x == T) return a; int used = 0, f; for(int i = h[x]; i; i = edge[i].next){ int v = edge[i].to; if(d[v] == d[x] + 1){ f = DFS(v, min(edge[i].w, a - used)); used += f; edge[i].w -= f; edge[i^1].w += f; if(used == a)return used; } } if(!used)d[x] = -1; return used; } int Max_Flow(){ int ret = 0; while(BFS()) ret += DFS(S, inf); return ret; } //---------------------------------------------------------------------// int in[maxn][maxn], out[maxn][maxn]; const int dx[] = {0, -1, 1, 0, 0}; const int dy[] = {0, 0, 0, 1, -1}; const char ch[] = " AV><"; int M[500]; int main(){ freopen("archery.in", "r", stdin); freopen("archery.out", "w", stdout); M['A'] = 1, M['V'] = 2, M['>'] = 3, M['<'] = 4; memset(a, 0, sizeof a); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++){ scanf("%s", a[i] + 1); a[i][m+1] = 0; } int idf = 0 ; for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++){ in[i][j] = ++ idf, out[i][j] = ++ idf; add(in[i][j], out[i][j], inf); } int ans = 0, dir, x, y, t; S = 0, T = ++ idf; for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++){ if(dir = M[a[i][j]]){ if(dir < 3)add(S, in[i][j], inf); else add(out[i][j], T, inf); for(t = 0, x = i, y = j; in[x][y]; x += dx[dir], y += dy[dir]) if(a[x][y] >= '0' && a[x][y] <= '9') t = max(t, a[x][y] ^ 48); ans += t; x = i + dx[dir], y = j + dy[dir]; if(a[x][y]){ if(dir < 3)add(in[i][j], in[x][y], t); else add(out[x][y], out[i][j], t); for(; in[x+dx[dir]][y+dy[dir]]; x += dx[dir], y += dy[dir]){ int w = (a[x][y] >= '0' && a[x][y] <= '9') ? t - a[x][y] + 48: t; if(dir < 3)add(in[x][y], in[x+dx[dir]][y+dy[dir]], w); else add(out[x+dx[dir]][y+dy[dir]], out[x][y], w); } } } } printf("%d\n", ans - Max_Flow()); return 0; }
另外一道求最小割的神题
勇闯法法塔(tower)
【题目描述】
法法塔位于法法城西南,共有5层,是战狂用来发动大型魔法的武器。飞刀杂耍者和小象引开守卫后,小奇和蛤布斯潜入了法法塔。
法法塔第1层左侧有一个n*m的魔法阵,魔法阵的每一格刻有“#”或“.”。蛤布斯发现,要破解魔法阵,需要对每个“#”施放炎爆术,但不能攻击到“.”。蛤布斯法力高超,每次可以同时对一行(列)的连续一段格子施放炎爆术。
为了尽快破解魔法阵,蛤布斯希望你帮他求出他最少需要施放多少次炎爆术。
【读入数据】
n行,每行一个长度为m的字符串。
【读出数据】
一行一个数表示答案。
【样例读入】
.##.####.####.#########.##..
##.#.####################.##
.##.###.##.###.###.###.###..
#..###..#########..###.##.##
####..#######.#.#####.######
##.######.#..#.#############
##.######.###########.######
#######.#######.#..###.#.###
#####..#######.#####.#.###.#
#..#################.#.####.
##.######..#.#####.######.##
..#.#############.#.##....#.
....##..##..#.#####.#.###.##
##.#########...#..#.#.######
##.#..###########..#..####.#
#.####.###.########.########
#####.#########.##.##.######
.##.####..###.###...######.#
【样例输出】
88
Sol:
问题等价于给每个格子赋上“横”或“竖”,代价就是(赋上的方向下一格是墙的格子个数+方向不同的相邻格子对数)/2。跑一下最小割就行了。
时间复杂度O(maxflow(nm,nm))
我们可以考虑如果一个点属于S集代表选择横向,属于T集代表选择纵向,那么选择横向时我们要把这个点连接T并且代价为这个点横向两边是否是'.'。纵向同理,当两个相邻的格子方向不同时代价为1。
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 51 #define maxnode 5010 using namespace std; const int inf = 0x7fffffff; char s[maxn][maxn]; int n, m, idfclock, id[maxn][maxn]; struct Edge{int to, nxt, w;}edge[2000010]; int h[maxnode], cnt = 1; void add(int u, int v, int w){ if(w == 0)return; edge[++ cnt] = (Edge){v, h[u], w}; h[u] = cnt; edge[++ cnt] = (Edge){u, h[v], 0}; h[v] = cnt; } int S, T, d[maxnode], que[maxnode]; bool BFS(){ memset(d, -1, sizeof d); d[S] = 0; int head = 0, tail = 0; que[tail ++] = S; while(head != tail){ int u = que[head ++]; for(int i = h[u]; i; i = edge[i].nxt){ if(edge[i].w == 0)continue; int v = edge[i].to; if(d[v] == -1)d[v] = d[u] + 1, que[tail ++] = v; } }return d[T] != -1; } int DFS(int x, int a){ if(x == T || a == 0)return a; int used = 0, f; for(int i = h[x]; i; i = edge[i].nxt){ int v = edge[i].to; if(d[v] == d[x] + 1){ f = DFS(v, min(edge[i].w, a - used)); edge[i].w -= f; edge[i ^ 1].w += f; used += f; if(used == a)return used; } } if(used == 0)d[x] = -1; return used; } int Dinic(){ int ret = 0; while(BFS()) ret += DFS(S, inf); return ret; } bool check(int x, int y){ return x > 0 && y > 0 && x <= n && y <= m && s[x][y] == '#'; } const int dx[] = {0, 0, 1, -1}; const int dy[] = {1, -1, 0, 0}; int main(){ freopen("tower.in", "r", stdin); freopen("tower.out", "w", stdout); while(~ scanf("%s", s[++ n] + 1)); n --, m = strlen(s[1] + 1); for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++) if(s[i][j] == '#')id[i][j] = ++ idfclock; int ans = idfclock; S = 0, T = ++ idfclock; for(int i = 1; i <= n; i ++){ for(int j = 1; j <= m; j ++){ if(s[i][j] == '#'){ add(id[i][j], T, (!check(i, j - 1)) + (!check(i, j + 1))); add(S, id[i][j], (!check(i - 1, j)) + (!check(i + 1, j))); for(int k = 0; k < 4; k ++){ int x = i + dx[k], y = j + dy[k]; if(check(x, y))add(id[i][j], id[x][y], 1); } } } } printf("%d\n", Dinic() / 2); return 0; }
选数
【问题描述】
有n 个正整数对,其中第i 个数对为(ai, bi)。请你选出一些数对,使得选出
的任意两个数对(记为第i 对和第j 对)满足以下两个条件之一:
1. 不存在正整数T 使得ai ^ 2 + aj ^ 2 = T ^2
2. ai 和aj 有大于1 的公因子。
选择第i 个数对带来的收益为bi。求收益总和的最大值。
Sol:最小割
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #define maxn 2010 #define In(x) x #define Out(x) x + n using namespace std; typedef long long ll; const int inf = 0x7fffffff; int n, S, T, a[maxn], b[maxn]; ll hs[2000010]; int h[maxn], cnt = 1; struct Edge{int to, nxt, w;}edge[2000010]; void add(int u, int v, int w){ edge[++ cnt] = (Edge){v, h[u], w}; h[u] = cnt; edge[++ cnt] = (Edge){u, h[v], 0}; h[v] = cnt; } int gcd(int a, int b){return !b ? a : gcd(b, a % b);} int que[maxn], d[maxn]; bool BFS(){ int head = 0, tail = 0; memset(d, -1, sizeof d); d[S] = 0, que[tail ++] = S; while(head != tail){ int u = que[head ++]; for(int i = h[u]; i; i = edge[i].nxt){ if(edge[i].w == 0)continue; int v = edge[i].to; if(d[v] == -1)d[v] = d[u] + 1, que[tail ++] = v; } }return d[T] != -1; } int DFS(int x, int a){ if(x == T || a == 0)return a; int used = 0, f; for(int i = h[x]; i; i = edge[i].nxt){ int v = edge[i].to; if(d[v] == d[x] + 1){ f = DFS(v, min(a - used, edge[i].w)); edge[i].w -= f; edge[i ^ 1].w += f; used += f; if(a == used)return used; } } if(used == 0)d[x] = -1; return used; } int Dinic(){ int ret = 0; while(BFS()) ret += DFS(S, inf); return ret; } int main(){ freopen("number.in", "r", stdin); freopen("number.out", "w", stdout); scanf("%d", &n); int mx = 0; for(int i = 1; i <= n; i ++){ scanf("%d", &a[i]); mx = max(mx, a[i]); } mx *= 2; for(int i = 1; i <= n; i ++) scanf("%d", &b[i]); for(int i = 1; i <= mx; i ++) hs[i] = (ll)i * i; int ans = 0; S = 0, T = 2 * n + 1; for(int i = 1; i <= n; i ++){ ans += b[i]; add(S, In(i), b[i]), add(Out(i), T, b[i]); for(int j = i + 1; j <= n; j ++){ if(gcd(a[i], a[j]) == 1){ ll nw = (ll)a[i] * a[i] + (ll)a[j] * a[j]; if(*lower_bound(hs + 1, hs + 1 + mx, nw) == nw) add(In(i), Out(j), inf), add(In(j), Out(i), inf); } } } printf("%d\n", ans - Dinic() / 2); return 0; }
求一个0,1,2的图中选出L型字符的最大个数