创世纪

题目概述

上帝手中有 N105N \le 10^5 种世界元素,每种元素可以限制另外 11 种元素,把第 ii 种世界元素能够限制的那种世界元素记为 A[i]A[i]

现在,上帝要把它们中的一部分投放到一个新的空间中去建造世界。

为了世界的和平与安宁,上帝希望所有被投放的世界元素都有至少一个没有被投放的世界元素限制它。

上帝希望知道,在此前提下,他最多可以投放多少种世界元素?

感觉网上的贪心题解都稍有问题?

单纯考虑一个树(不是基环树),发现可直接从子节点贪心得出父节点状态

首先,这是一个内向树森林

考虑一颗内向基环树 TT,环上节点为 s1,s2,,sts_1, s_2, \cdots,s_t

对每一个环上节点和其子树分开执行贪心(这只是描述,实际上用拓扑排序递推)

  1. 如果 TT 是一个环,则 t/2t/2
  2. 如果存在一个 sis_i 由贪心过程得出它可以投放,则环就会断成链
  3. 如果不存在,则环还是完整的,t/2t/2

贪心过程由拓扑排序执行,讨论 2 可以直接在这个过程中完成

阅读更多

树上差分

首先,树上差分是从子节点推到根节点,定义 dd 差分数组

若修改 (x,y)(x,y) 路径上的点权,则 dx,dy+=k,dlca,dfalca=kd_x, d_y \mathrel{+}= k, d_{\mathrm{lca}}, d_{\mathrm{fa}_{\mathrm{lca}}} \mathrel{-}= k

1
2
3
4
5
6
void dfs(int u){
val[u] = d[u];
for (auto v : g[u]){
dfs(v), val[u] += val[v];
}
}

若修改 (x,y)(x,y) 路径上的边权,则 dx,dy+=k,dlca=2kd_x, d_y \mathrel{+}= k, d_{\mathrm{lca}}\mathrel{-}= 2k

1
2
3
4
5
6
7
8
int dfs(int u){
int ret = d[u];
for (auto i : g[u]){
i.w = dfs(i.v);
ret += i.w;
}
return ret;
}

P3128 最大流

阅读更多

Network,闇の連鎖

仔细分析,考虑树上,若断一条树边 (u,v)(u, v),则会分成两个连通块

  • 两个连通块之间有 00 条附加边,则任意再切一条
  • 两个连通块之间有 11 条附加边,则只能切特定的一条
  • 两个连通块之间有 2\geq 2 条附加边,无方案

如何快速计数?

记附加边为 (x,y)(x,y) ,发现 (u,x),(v,y)(u, x),(v,y) 两条路径不相交则 (x,y)(x,y)(u,v)(u,v) 有影响

即,附加边 (x,y)(x,y) 对于主要边树上 (x,y)(x,y) 路径上所有边都有影响

或者说,附加边 (x,y)(x,y) 会在树上产生环,如果切环上的边就必须把 (x,y)(x,y) 切掉

然后就是树上边差分

POJ 上 TLE 了,但是本地没问题,复杂度(应该)没挂?

阅读更多

求 LCA 的 Tarjan 算法

这是一个复杂度为 O(n+q)O(n+q) 的离线求 LCA 算法

用 dfs 进行遍历并打 tag

假设遍历到 uu,则已回溯的节点标为 22,遍历到但未回溯的节点(即 uuuu 的祖先)标 11,其余为 00

现在假设 uu 的子树处理完毕,考虑 uu 的处理

询问节点为 (u,v)(u, v)

此时,若 tagv=2\mathrm{tag}_v = 2,则可以保证 vv 向根走遇到的第一个 tag=1\mathrm{tag} = 1 的节点即为 LCA(u,v)\mathrm{LCA}(u,v),可以用反证法证明

若向根走遇到的第一个 tag=1\mathrm{tag} = 1 的节点,记为 wwwLCA(u,v)w \not = \mathrm{LCA}(u,v)

LCA(u,v)\mathrm{LCA}(u,v)pp

显然 ppuu 到根的路径上

ppww 下方,则 tagp1\mathrm{tag}_p \not= 1,但是 pp 又为 uu 的祖先,tag=1\mathrm{tag}=1

矛盾

ppww 上方,根据算法可得 wwvv 的 祖先,又 tagw=1\mathrm{tag}_w = 1wwuu 的祖先,所以 ww 必为 uuvv 的公共祖先,但是 ppww 的祖先

矛盾

证毕

下面的程序复杂度应该没有问题,但是要卡一下常,吸一口氧才能过

阅读更多

「APIO 2010」巡逻

容易发现,若不加新边,则总距离为 2(n1)2(n-1)

发现 k=1k=1 时,连一条边等于环上的边只需走一次,显然最优连直径

发现 k=2k=2 时,再连一条边,若构成的环和第一次的环不相交,则可以直接减

若相交,则需要经过重叠部分两次,可以通过把边权改成 1-1 实现

对于新边能走一次,可以用,不实际在图上加边,来控制

阅读更多

「USACO 2011 Jan. Gold」道路和航线

题目概述

LOJ #10081. 「一本通 3.2 练习 7」道路和航线 原题来自:USACO 2011 Jan. Gold

Farmer John 正在一个新的销售区域对他的牛奶销售方案进行调查。他想把牛奶送到 TT 个城镇 ,编号为 11TT。这些城镇之间通过 RR 条道路(编号为 11RR)和 PP 条航线(编号为 11PP)连接。每条道路 ii 或者航线 ii 连接城镇 AiA_iBiB_i,花费为 CiC_i

对于道路,0Ci1040 \le C_i \le 10^4,然而航线的花费很神奇,花费 CiC_i 可能是负数。道路是双向的,可以从 AiA_iBiB_i,也可以从 BiB_iAiA_i,花费都是 CiC_i。然而航线与之不同,只可以从 AiA_iBiB_i

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策保证:如果有一条航线可以从 AiA_iBiB_i,那么保证不可能通过一些道路和航线从 BiB_i 回到 AiA_i。由于 FJ 的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。他想找到从发送中心城镇 SS 把奶牛送到每个城镇的最便宜的方案,或者知道这是不可能的。

如果去掉航线可以发现原图变成了许多连通块

若把一个连通块看成一个节点,则发现连通块和航线组成 DAG

考虑 Topo sort 结合 Dijkstra

称连通块和航线组成的图为 GG,连通块 AA 内部节点和道路的图为 gAg_A

假设 ss 所在的连通块为 SS,对所有在 GG 中可从 SS 到达的连通块进行拓扑排序(否则算 deg 的时候会算多),一边拓扑一边 Dijkstra

大致是这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
q <- Topo_Queue;
while(q not empty):
clr = q.front(); q.pop();

heap <- Dijkstra_Priority_Queue;
for (u 为 clr 节点)
if (dis[u] != INF) heap.push(dis[u], u);

while(heap not empty):
u = heap.top(); heap.pop();
for 从 u 出发道路:
// 普通 Dijkstra 写法,略
for 从 u 出发的航线(u, v, w):
A 为 v 所在的连通块;
if (A 不可从 S 出发到达) continue;
deg[A]--;
if (deg[A] == 0) q.push(A);
dis[v] = min(dis[v], dis[u] + w)
阅读更多

「NOIP 2009」最优贸易

题目概述

LOJ #2590. 「NOIP2009」最优贸易 C 国有 nn 个大城市和 mm 条道路,每条道路连接这 nn 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 mm 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 11 条。

C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 nn 个城市的标号从 1n1\sim n,阿龙决定从 11 号城市出发,并最终在 nn 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 nn 个城市。

阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

假设 C 国有 55 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

27.png

假设 11 ~ nn 号城市的水晶球价格分别为 4,3,5,6,14,3,5,6,1

阿龙可以选择如下一条线路:12351\to 2\to 3\to 5,并在 22 号城市以 33 的价格买入水晶球,在 33 号城市以 55 的价格卖出水晶球,赚取的旅费数为 22

阿龙也可以选择如下一条线路 145451\to 4\to 5\to 4\to 5,并在第 11 次到达 55 号城市时以 11 的价格买入水晶球,在第 22 次到达 44 号城市时以 66 的价格卖出水晶球,赚取的旅费数为 55

现在给出 nn 个城市的水晶球价格, mm 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

解法一

Tarjan 缩点, 重构图 + 判可达性(某一个强连通分量能否到达终点,若不能,则不能更新 ans ) + 跑 DAG 上的 DP

用了一个结论:Tarjan 逆序即为拓扑序

具体看代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <deque>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
#define per(i, l, r) for (int i = (l); i >= (r); --i)
using std::cerr;
using std::cin;
using std::cout;
using std::endl;
using std::make_pair;
using std::pair;
typedef pair<int, int> pii;
typedef long long ll;
typedef unsigned int ui;

const int N = 100005;

std::vector<int> g[N], ng[N], fg[N];
int price[N];

int min[N], max[N], dfn[N], ins[N], low[N], color[N], dp[N];
std::stack<int> s;
int cnt = 0;
int T = 0;

void tarjan(int u) {
dfn[u] = low[u] = ++T, ins[u] = 1;
s.push(u);
for (auto v : g[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = std::min(low[u], low[v]);
} else if (ins[v]) {
low[u] = std::min(low[u], low[v]);
}
}
if (dfn[u] == low[u]) {
int x;
cnt++;
// cerr << "cnt" << cnt << ":\n";
do {
x = s.top();
s.pop();
// cerr << x << ' ';
color[x] = cnt, ins[x] = 0;
min[cnt] = std::min(min[cnt], price[x]);
max[cnt] = std::max(max[cnt], price[x]);
} while (x != u);
// cerr << endl;
}
}

int coutable[N];

void dfs(int u) {
if (coutable[u]) return;
coutable[u] = 1;
for (auto v : fg[u]) dfs(v);
}

int main() {
#ifdef LOCAL
freopen("input", "r", stdin);
#endif
std::memset(min, 0x3f, sizeof(min));
std::ios::sync_with_stdio(false);
cout.tie(0);
int n, m;
cin >> n >> m;
rep(i, 1, n) cin >> price[i];
rep(i, 1, m) {
int x, y, z;
cin >> x >> y >> z;
g[x].push_back(y);
if (z - 1) g[y].push_back(x);
}

// tarjan
tarjan(1);

// rebuild
rep(u, 1, n) {
for (auto v : g[u])
if (color[u] != color[v]) ng[color[u]].push_back(color[v]);
}

// connectable
rep(u, 1, cnt) for (auto v : ng[u]) fg[v].push_back(u);
dfs(color[n]);

// topo + dp
int ans = 0;
std::memset(dp, 0x3f, sizeof(dp));
per(u, cnt, 1) {
dp[u] = std::min(dp[u], min[u]);
if (coutable[u]) ans = std::max(ans, max[u] - dp[u]);
for (auto v : ng[u]) {
dp[v] = std::min(dp[v], dp[u]);
}
}

cout << ans;
return 0;
}

解法二

正图反图跑 SPFA转移方程定义为

dv=min(minuv{du},pricev) d_{v} = \min\Big(\min_{u\rightarrow v}\{d_u\}, price_v\Big)

最后减一下

解法三

引用一下

构造分层图,具体看上面

Miller Rabin 判素

数学

费马小定理

对于素数 pp 必存在

ap11(modp) a^{p-1} \equiv 1 \pmod{p}

所以可以随机选取 a[2,n1]a\in[2,n-1] 检验是否满足上式,若不满足,则必然是合数

遗憾的是,存在无穷多个 nn,可以满足

 x[2,n1]xn11(modn) \forall\ x\in[2,n-1]\\ x^{n-1}\equiv 1 \pmod{n}

也就是说使费马判素失效

二次探测定理

素数 pp 必满足方程

x21(modp) x^2\equiv1\pmod{p}

有且仅有两个解

x11, x2p1(modp) x_1\equiv1,\ x_2\equiv p-1\pmod{p}

实现

n1n-1 分解成 d2td\cdot2^t 使 tt 尽量大

然后 xbased(modn)x\equiv base^{d} \pmod{n}

然后不断进行二次探测

即如果满足 x≢1x\not\equiv 1x≢n1x\not\equiv n-1x21x^2\equiv 1 那么这个数解不是素数了

最后进行费马判素

1
2
3
4
5
6
7
8
9
10
11
12
13
bool miller_rabin(ll n) {
if (n <= 2) return n == 2;
int d = n - 1, t = 0;
while (d % 2 == 0) d /= 2, ++t;
rep(i, 0, T) { // T 是测试次数, p 是测试基
ll x = ksm(p[i] % (n - 2) + 2, d, n);
if (x == 1 || x == n - 1) continue; // 无效的二次探测
for (int j = 0; j < t; ++j, x = x * x % n)
if (x != n - 1 && x != 1 && x * x % n == 1) return 0;
if (x != 1) return 0; // 费马判素
}
return 1;
}

这是测试基选择的推荐

数据范围 测试基
2322^{32} 2,7,61
2642^{64} 2,3,5,7,11,13,17,19,23,29,31,37

「一本通 1.1 练习 4」家庭作业 题解

题目概述

LOJ #10008. 「一本通 1.1 练习 4」家庭作业

老师在开学第一天就把所有作业都布置了,每个作业如果在规定的时间内交上来的话才有学分。每个作业的截止日期和学分可能是不同的。例如如果一个作业学分为 1010,要求在 66 天内交,那么要想拿到这 1010 学分,就必须在第 66 天结束前交。

每个作业的完成时间都是只有一天。例如,假设有 7 次作业的学分和完成时间如下:

作业号 期限 学分
11 11 66
22 11 77
33 33 22
44 33 11
55 22 44
66 22 55
77 66 11

最多可以获得 1515 学分,其中一个完成作业的次序为 2,6,3,1,7,5,42,6,3,1,7,5,4,注意可能还有其他方法。

你的任务就是找到一个完成作业的顺序获得最大学分。

显然的贪心,先按学分 ss 从大到小,再按期限 dd 从后到前

考虑要安排一个作业,显然是越晚越好,故用一个二分区间,用一个数据结构查询、修改

这里选择二分套树状数组

阅读更多

粒子群算法

概述

这是一种随机算法

模仿鸟类捕食

在定义域中随机撒 nn 个点,需要维护(以二维定义域为例)

  • 坐标 p(x,y)p(x,y)
  • 速度 vv ,一个二维向量
  • 历史最优值 pbpb 和其坐标

同时还要维护全局最优解及其坐标

阅读更多