[ZJOI2007]时态同步 题解
题目地址:洛谷:【P1131】[ZJOI2007]时态同步 – 洛谷、BZOJ …
May all the beauty be blessed.
题目地址:洛谷:【P3224】[HNOI2012]永无乡 – 洛谷、BZOJ:Problem 2733. — [HNOI2012]永无乡
永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连通的。
现在有两种操作:
B x y 表示在岛 x 与岛 y 之间修建一座新桥。
Q x k 表示询问当前与岛 x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪座,请你输出那个岛的编号。
给你一个$n$个点有点权的图,两种操作:
输入格式:
输入文件第一行是用空格隔开的两个正整数 n 和 m,分别表示岛的个数以及一开始存在的桥数。
接下来的一行是用空格隔开的 n 个数,依次描述从岛 1 到岛 n 的重要度排名。
随后的 m 行每行是用空格隔开的两个正整数 ai 和 bi,表示一开始就存在一座连接岛 ai 和岛 bi 的桥。
后面剩下的部分描述操作,该部分的第一行是一个正整数 q,表示一共有 q 个操作,接下来的 q 行依次描述每个操作,操作的格式如上所述,以大写字母 Q 或 B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。
输出格式:
对于每个 Q x k 操作都要依次输出一行,其中包含一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出-1。
输入样例#1:
5 1 4 3 2 5 1 1 2 7 Q 3 2 Q 2 1 B 2 3 B 1 5 Q 2 1 Q 2 4 Q 2 3
输出样例#1:
-1 2 5 1 2
对于 20% 的数据 n≤1000, q≤1000
对于 100% 的数据 n≤100000, m≤n, q≤300000
其实把图上的连通块看成集合就是个裸题了。我们考虑使用Splay维护集合,加边等价于合并两个集合,启发式合并即可,询问$k$小直接做就好。注意已经是一个集合内的情况不需要加边,而且加边存在数据不合法(即$a_i, b_i < 1$或$a_i, b_i > n$的情况),需要把这些边忽略。
复杂度$O(n \log n)$。
// Code by KSkun, 2018/7
#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 * 10 + c - '0';
return res * neg;
}
inline char readsingle() {
register char c;
while(!isgraph(c = fgc())) {}
return c;
}
const int MAXN = 100005;
int n, m, q;
struct Node {
int ch[2], fa, val, siz;
} tr[MAXN];
int tot;
inline bool isleft(int p) {
return tr[tr[p].fa].ch[0] == p;
}
inline void update(int p) {
if(!p) return;
tr[p].siz = tr[tr[p].ch[0]].siz + tr[tr[p].ch[1]].siz + 1;
}
inline void rotate(int p) {
bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
tr[p].fa = ffa; if(ffa) 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) {
for(int fa = tr[p].fa; fa; rotate(p), fa = tr[p].fa) {
if(tr[fa].fa) rotate(isleft(fa) == isleft(p) ? fa : p);
}
update(p);
}
inline int insert(int rt, int q) {
int p = rt;
for(;;) {
bool t = tr[q].val > tr[p].val;
if(!tr[p].ch[t]) {
tr[p].ch[t] = q; tr[q].fa = p; tr[q].ch[0] = 0; tr[q].ch[1] = 0;
splay(q); return q;
}
p = tr[p].ch[t];
}
}
inline int queryk(int rt, int k) {
int p = rt;
for(;;) {
if(k <= tr[tr[p].ch[0]].siz) {
p = tr[p].ch[0];
} else if(k == tr[tr[p].ch[0]].siz + 1) {
splay(p); return p;
} else {
k -= tr[tr[p].ch[0]].siz + 1;
p = tr[p].ch[1];
}
}
}
int sta[MAXN], stop;
void dfs(int p) {
sta[stop++] = p;
if(tr[p].ch[0]) dfs(tr[p].ch[0]);
if(tr[p].ch[1]) dfs(tr[p].ch[1]);
}
inline void merge(int x, int y) {
splay(x); splay(y);
if(tr[x].fa || tr[y].fa || x == y) return;
if(tr[x].siz > tr[y].siz) std::swap(x, y);
stop = 0; dfs(x);
for(int i = 0; i < stop; i++) {
splay(y); insert(y, sta[i]);
}
}
int main() {
n = readint(); m = readint();
for(int i = 1; i <= n; i++) {
tr[i].val = readint();
tr[i].siz = 1;
}
for(int i = 1, a, b; i <= m; i++) {
a = readint(); b = readint();
if(a < 1 || a > n || b < 1 || b > n) continue;
merge(a, b);
}
q = readint();
while(q--) {
char op = readsingle();
int a = readint(), b = readint();
if(op == 'B') {
merge(a, b);
} else {
splay(a);
if(b > tr[a].siz) puts("-1");
else printf("%d\n", queryk(a, b));
}
}
return 0;
}
题目地址:BZOJ:Problem 2438. — [中山市选2011]杀人游戏
一位冷血的杀手潜入 Na-wiat,并假装成平民。警察希望能在 N 个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人, 谁是杀手, 谁是平民。 假如查证的对象是杀手,杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?
有$n$个人,其中一个人是杀手,警察知道了每个人认识的人的情况,询问一个人,如果他是平民,则会告诉警察他认识的人的身份,如果是杀手警察会被杀死。现在每个人是平民或杀手的概率是相等的,求在最优情况下,警察安全且得到答案的概率。
输入格式:
第一行有两个整数 N,M。
接下来有 M 行,每行两个整数 x,y,表示 x 认识 y(y 不一定认识 x,例如[一个宽为$w$高为$h$的矩形的面积])。
输出格式:
仅包含一行一个实数,保留小数点后面 6 位,表示最大概率。
输入样例#1:
5 4 1 2 1 3 1 4 1 5
输出样例#1:
0.800000
警察只需要查证 1。假如1是杀手,警察就会被杀。假如 1不是杀手,他会告诉警察 2,3,4,5 谁是杀手。而 1 是杀手的概率是 0.2,所以能知道谁是杀手但没被杀的概率是0.8。
对于 100%的数据有 1≤N ≤ 10 0000,0≤M ≤ 30 0000
首先,对于一个强连通分量里的点,只需要询问分量中一个不是杀手的人,那么这个分量及它连接的分量的信息都可以安全地知道,因此我们可以考虑缩点然后统计入度为0的点有多少个。
然后你就WA了,这是因为,如果$n-1$个点的信息都知道了,那么第$n$个点不用询问也可以知道,可以少冒一次险问一个未知的人。满足这样条件的点一定是在缩点后的新图中大小为1,入度为0且连接的分量入度都不小于2的这样的分量。
那么假如最后求出需要冒险询问的人数是$x$,那么概率就是$\frac{n-x}{n}$。
复杂度$O(n+m)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <set>
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 * 10 + c - '0';
return res * neg;
}
const int MAXN = 100005;
int n, m;
std::vector<int> gra[MAXN], gran[MAXN];
int deg[MAXN];
int dfn[MAXN], low[MAXN], clk, sno[MAXN], ssiz[MAXN], scc;
std::stack<int> sta;
bool insta[MAXN];
void tarjan(int u) {
dfn[u] = low[u] = ++clk;
sta.push(u); insta[u] = true;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(!dfn[v]) {
tarjan(v);
low[u] = std::min(low[u], low[v]);
} else if(insta[v]) {
low[u] = std::min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u]) {
int p; scc++;
do {
p = sta.top(); sta.pop();
insta[p] = false;
sno[p] = scc;
ssiz[scc]++;
} while(p != u);
}
}
typedef std::pair<int, int> PII;
std::set<PII> edges;
inline bool check(int u) {
if(deg[u] != 0 || ssiz[u] != 1) return false;
for(int i = 0; i < gran[u].size(); i++) {
int v = gran[u][i];
if(deg[v] < 2) return false;
}
return true;
}
int main() {
n = readint(); m = readint();
for(int i = 1, u, v; i <= m; i++) {
u = readint(); v = readint();
gra[u].push_back(v);
}
for(int i = 1; i <= n; i++) {
if(!dfn[i]) tarjan(i);
}
for(int u = 1; u <= n; u++) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(sno[u] != sno[v] && !edges.count(PII(sno[u], sno[v]))) {
gran[sno[u]].push_back(sno[v]);
deg[sno[v]]++;
edges.insert(PII(sno[u], sno[v]));
}
}
}
int cnt = 0;
for(int i = 1; i <= scc; i++) {
if(!deg[i]) cnt++;
}
for(int i = 1; i <= scc; i++) {
if(check(i)) {
cnt--; break;
}
}
printf("%.6lf", double(n - cnt) / n);
return 0;
}
题目地址:POJ:3352 — Road Construction
It’s almost summer time, and that means that it’s almost summer construction time! This year, the good people who are in charge of the roads on the tropical island paradise of Remote Island would like to repair and upgrade the various roads that lead between the various tourist attractions on the island.
The roads themselves are also rather interesting. Due to the strange customs of the island, the roads are arranged so that they never meet at intersections, but rather pass over or under each other using bridges and tunnels. In this way, each road runs between two specific tourist attractions, so that the tourists do not become irreparably lost.
Unfortunately, given the nature of the repairs and upgrades needed on each road, when the construction company works on a particular road, it is unusable in either direction. This could cause a problem if it becomes impossible to travel between two tourist attractions, even if the construction company works on only one road at any particular time.
So, the Road Department of Remote Island has decided to call upon your consulting services to help remedy this problem. It has been decided that new roads will have to be built between the various attractions in such a way that in the final configuration, if any one road is undergoing construction, it would still be possible to travel between any two tourist attractions using the remaining roads. Your task is to find the minimum number of new roads necessary.
有一个连通图,问至少加几条边可以让它变成一个边双连通图。
输入格式:
The first line of input will consist of positive integers n and r, separated by a space, where 3 ≤ n ≤ 1000 is the number of tourist attractions on the island, and 2 ≤ r ≤ 1000 is the number of roads. The tourist attractions are conveniently labelled from 1 to n. Each of the following r lines will consist of two integers, v and w, separated by a space, indicating that a road exists between the attractions labelled v and w. Note that you may travel in either direction down each road, and any pair of tourist attractions will have at most one road directly between them. Also, you are assured that in the current configuration, it is possible to travel between any two tourist attractions.
输出格式:
One line, consisting of an integer, which gives the minimum number of roads that we need to add.
输入样例#1:
10 12 1 2 1 3 1 4 2 5 2 6 5 6 3 7 3 8 7 8 4 9 4 10 9 10
输出样例#1:
2
输入样例#2:
3 3 1 2 2 3 1 3
输出样例#2:
0
先对边双缩点,缩点后的边双是一棵树,只要把树上的叶子两两连起来就可以把链合并成一个环,成为更大的边双。因此答案是(叶子数量+1)/2。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#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 = 10005;
int n, m;
std::vector<int> gra[MAXN];
int dfn[MAXN], low[MAXN], clk, deg[MAXN];
void tarjan(int u, int fa) {
dfn[u] = low[u] = ++clk;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(v == fa) continue;
if(!dfn[v]) {
tarjan(v, u);
low[u] = std::min(low[u], low[v]);
} else {
low[u] = std::min(low[u], dfn[v]);
}
}
}
int main() {
n = readint(); m = readint();
for(int i = 1, u, v; i <= m; i++) {
u = readint(); v = readint();
gra[u].push_back(v);
gra[v].push_back(u);
}
tarjan(1, 0);
for(int u = 1; u <= n; u++) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(low[u] != low[v]) {
deg[low[u]]++;
}
}
}
int cnt = 0;
for(int i = 1; i <= n; i++) {
if(deg[i] == 1) cnt++;
}
printf("%d", (cnt + 1) / 2);
return 0;
}