[中山OI2011]杀人游戏 题解
题目地址:BZOJ:Problem 2438. — [中山市选2011]杀人游 …
May all the beauty be blessed.
题目地址:洛谷:【P4700】[CEOI2011]Traffic – 洛谷、BZOJ:Problem 2387. — [Ceoi2011]Traffic
格丁尼亚的中心位于Kacza河中的一座岛屿。每天清晨,成千上万辆汽车通过岛屿从西岸的住宅区(由桥连接岛的西部)到东岸的工业区(由桥连接岛的东部)。该岛类似于矩形,它的边平行于主方向。故可将它看作是笛卡尔坐标系中的一个A*B的矩形,它的对角分别为(0, 0)和(A, B)。岛上有n个交通节点,编号为1…n(junction,此处可理解为广义的路口),第i个节点坐标为(xi, yi)。如果一个节点的坐标为(0, y),它就位于岛的西岸。类似的,坐标为(A, y)的节点位于岛的东岸。各个节点由街道连接,每条街道用线段连接两个节点。街道有单向行驶或双向行驶之分。除端点外任意两条街道都没有公共点。也没有桥梁或者隧道。你不能对道路网络形状做任何其他假设。沿河岸的街道或节点可能没有入口或者出口街道。由于交通堵塞日趋严重,市长聘请你测试岛上当前的道路网是否足够。要求你写一个程序确定从岛的西岸的每个节点能够到达东岸的多少个节点。
在平面直角坐标系上有 n 个点,其中第 i 个点的坐标是 (xi,yi) ,所有点在一个以 (0,0) 和 (A,B) 为相对顶点的矩形内。
如果 xi=0 ,那么我们称这个点在西侧。如果 xi=A ,那么我们称这个点在东侧。
这些点之间有 m 条边,每条边可能是有向边也可能是无向边,保证边在交点以外的任何地方不相交。
现在请你求出,对于每一个西侧的点,能够沿着边到达多少东侧的点。
输入格式:
第1行包含4个整数n, m, A, B(1≤n≤300000, 0≤m≤900000,1≤A,B≤109),
分别表示格丁尼亚市中心的节点数,街道数和岛屿的尺寸。
接下来的n行,每行包含两个整数xi,yi (0≤xi≤A,0≤yi≤B),表示第i个节点的坐标。任意两个节点的坐标都不相同。
再往下的m行表示街道,每条街道用3个整数ci, di, ki(1≤ci, di≤n, ci≠di, ki∈{1,2}),
表示节点ci、di有街道连接
如果ki=1,表示从ci到di的街道是单向的,否则,这条街道可以双向行驶。每个无序对{ci, di}最多出现1次。
你可以假设西岸节点中至少有1个能够到达东岸的一些节点。
输出格式:
为每个西岸节点输出1行,包括从这个节点出发能够到达东岸的节点数目
输入样例#1:
5 3 1 3 0 0 0 1 0 2 1 0 1 1 1 4 1 1 5 2 3 5 2
输出样例#1:
2 0 2
输入样例#2:
12 13 7 9 0 1 0 3 2 2 5 2 7 1 7 4 7 6 7 7 3 5 0 5 0 9 3 9 1 3 2 3 2 1 3 4 1 4 5 1 5 6 1 9 3 1 9 4 1 9 7 1 9 12 2 10 9 1 11 12 1 12 8 1 12 10 1
输出样例#2:
4 4 0 2
$1 \leq n \leq 300000, 0 \leq m \leq 900000, 1 \leq A, B \leq 10^9$
首先显然可以对这个图缩一波强连通分量。注意到题目隐含了一个平面图的条件,对于一个平面图的左侧点,它能到达的右侧点应该在右侧是相邻的一段。因此,我们可以先扔掉那些不可达的右侧点,然后按照$y$坐标的顺序编号,用DFS来计算出缩点后的图中,每一个点对应的右侧区间左右端点,这样就可以计算出答案了。
复杂度$O(n \log n)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#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 << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 300005;
int n, m, A, B;
std::vector<int> gra[MAXN], gran[MAXN];
typedef std::pair<int, int> PII;
std::vector<int> lft, rgt;
PII node[MAXN];
int id[MAXN];
bool vis[MAXN];
int dfn[MAXN], low[MAXN], clk, sno[MAXN], scc, mx[MAXN], mn[MAXN];
bool insta[MAXN];
std::stack<int> sta;
std::set<PII> edges;
void tarjan(int u) {
dfn[u] = low[u] = ++clk;
insta[u] = true; sta.push(u);
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]) {
scc++;
int p;
do {
p = sta.top(); sta.pop();
insta[p] = false;
sno[p] = scc;
if(node[p].first == A) {
mx[scc] = std::max(mx[scc], id[p]);
mn[scc] = std::min(mn[scc], id[p]);
}
} while(p != u);
}
}
void dfs(int u) {
if(vis[u]) return;
vis[u] = true;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
dfs(v);
}
}
void dfs_m(int u) {
if(vis[u]) return;
vis[u] = true;
for(int i = 0; i < gran[u].size(); i++) {
int v = gran[u][i];
dfs_m(v);
mx[u] = std::max(mx[u], mx[v]);
mn[u] = std::min(mn[u], mn[v]);
}
}
inline bool cmp(int a, int b) {
return node[a].second > node[b].second;
}
int main() {
memset(mn, 0x3f, sizeof(mn));
n = readint(); m = readint(); A = readint(); B = readint();
for(int i = 1; i <= n; i++) {
int x = readint(), y = readint();
if(x == 0) lft.push_back(i);
if(x == A) rgt.push_back(i);
node[i] = PII(x, y);
}
for(int i = 1, u, v, k; i <= m; i++) {
u = readint(); v = readint(); k = readint();
gra[u].push_back(v);
if(k == 2) gra[v].push_back(u);
}
for(int i = 0; i < lft.size(); i++) {
int u = lft[i];
dfs(u);
}
std::sort(rgt.begin(), rgt.end(), cmp);
for(int i = 0; i < rgt.size(); i++) {
int u = rgt[i];
if(!vis[u]) rgt.erase(rgt.begin() + i), i--;
else id[u] = i + 1;
}
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]);
edges.insert(PII(sno[u], sno[v]));
}
}
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= scc; i++) dfs_m(i);
std::sort(lft.begin(), lft.end(), cmp);
for(int i = 0; i < lft.size(); i++) {
int u = lft[i];
printf("%d\n", std::max(0, mx[sno[u]] - mn[sno[u]] + 1));
}
return 0;
}
题目地址:洛谷:【P4055】[JSOI2009]游戏 – 洛谷、BZOJ:Problem 1443. — [JSOI2009]游戏Game
小AA和小YY得到了《喜羊羊和灰太狼》的电影票,都很想去观看,但是电影票只有一张,于是他们用智力游戏决定胜负,赢得游戏的人可以获得电影票。
在N*M的迷宫中有一个棋子,小AA首先任意选择棋子放置的位置。然后,小YY和小AA轮流将棋子移动到相邻的格子里。游戏的规则规定,在一次游戏中,同一个格子不能进入两次,且不能将棋子移动到某些格子中去。当玩家无法继续移动棋子时,游戏结束,最后一个移动棋子的玩家赢得了游戏。
例如下图所示的迷宫,迷宫中”.”表示棋子可以经过的格子,而”#”表示棋子不可以经过的格子:
.##
…
#.#
若小AA将棋子放置在(1,1),则小 AA 则无论如何都无法赢得游戏。
而若小AA将棋子放置在(3,2)或(2,3),则小AA能够赢得游戏。例如,小AA将棋子放置在(3,2),小YY只能将它移动到(2,2),此时小AA再将棋子移动到(2,3),就赢得了游戏。
小AA和小YY都是绝顶聪明的小朋友,且从不失误。小AA到底能不能赢得这场游戏,从而得到珍贵的电影票呢?
有一个$n \times m$矩形地图,有的格子不能走,每次选择一个起点,然后两个玩家轮流将放在起点的棋子移动一步,只能经过同一个格子一次,最后不能移动的玩家输,求是否后手必胜。
输入格式:
输入数据首先输入两个整数 N,M,表示了迷宫的边长。接下来 N 行,每行 M 个字符,描述了迷宫。
输出格式:
若小 AA 能够赢得游戏,则输出一行”WIN”,然后输出所有可以赢得游戏的起始位置,按行优先顺序输出,每行一个。否则输出一行”LOSE”(不包含引号)。
输入样例#1:
3 3 .## ... #.#
输出样例#1:
WIN 2 3 3 2
对30%的数据,有1<=n,m<=5
对100%的数据,有1<=n,m<=100
首先对矩形黑白染色,连边成为二分图,我们发现,可以利用匈牙利算法中的交错路来解决这个问题。首先跑一遍最大匹配。
如果为完美匹配的状况,当选择任何一个匹配点为出发点,则先手一定每次走一条匹配边,而最后一条边也是匹配边,先手必胜。
否则,先手从非匹配点出发,无论如何都会走到一个匹配点上,此时,后手会经历类似上面的情况,后手必胜。由于还要输出方案,我们还要找最大匹配时一定不是匹配点的点,可以从非匹配点出发寻找交错路,与非匹配点在二分图中同边的点都是答案。
// 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;
}
inline char readsingle() {
char c;
while(!isgraph(c = fgc())) {}
return c;
}
const int MAXN = 10005;
const int fix[][4] = {{1, 0, -1, 0}, {0, 1, 0, -1}};
int n, m;
std::vector<int> gra[MAXN];
int match[MAXN], vis[MAXN], clk;
bool dfs(int u) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(vis[v] != clk) {
vis[v] = clk;
if(!match[v] || dfs(match[v])) {
match[v] = u; match[u] = v; return true;
}
}
}
return false;
}
inline int bmatch() {
int res = 0;
for(int i = 1; i <= n * m; i++) {
if(!match[i]) {
clk++; if(dfs(i)) res++;
}
}
return res;
}
bool ans[MAXN];
void dfs_ans(int u) {
if(ans[u]) return;
ans[u] = true;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(match[v]) dfs_ans(match[v]);
}
}
inline int id(int x, int y) {
return (x - 1) * m + y;
}
inline int col(int x, int y) {
return (x + y) & 1;
}
char mmp[105][105];
int main() {
n = readint(); m = readint();
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
mmp[i][j] = readsingle();
}
}
int tot = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(mmp[i][j] == '#') continue;
tot++;
if(col(i, j)) continue;
for(int k = 0; k < 4; k++) {
int nx = i + fix[0][k], ny = j + fix[1][k];
if(nx < 1 || nx > n || ny < 1 || ny > m || mmp[nx][ny] == '#') continue;
gra[id(i, j)].push_back(id(nx, ny));
gra[id(nx, ny)].push_back(id(i, j));
}
}
}
if(bmatch() * 2 == tot) {
puts("LOSE"); return 0;
}
puts("WIN");
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(!match[id(i, j)]) {
dfs_ans(id(i, j));
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(mmp[i][j] != '#' && ans[id(i, j)]) {
printf("%d %d\n", i, j);
}
}
}
return 0;
}
题目地址:洛谷:【P3241】[HNOI2015]开店 – 洛谷、BZOJ:Problem 4012. — [HNOI2015]开店
风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。这样的想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有 n个地方,编号为 1 到 n,被 n-1 条带权的边连接起来。每个地方都住着一个妖怪,其中第 i 个地方的妖怪年龄是 x_i。妖怪都是些比较喜欢安静的家伙,所以它们并不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于 3。妖怪和人一样,兴趣点随着年龄的变化自然就会变化,比如我们的 18 岁少女幽香和八云紫就比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以幽香打算选择一个地方 u(u为编号),然后在 u开一家面向年龄在 L到R 之间(即年龄大于等于 L、小于等于 R)的妖怪的店。也有可能 u这个地方离这些妖怪比较远,于是幽香就想要知道所有年龄在 L 到 R 之间的妖怪,到点 u 的距离的和是多少(妖怪到 u 的距离是该妖怪所在地方到 u 的路径上的边的权之和),幽香把这个称为这个开店方案的方便值。幽香她们还没有决定要把店开在哪里,八云紫倒是准备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。
给你一棵$n$个点的二叉树,每个点有点权,每条边有边权,有$Q$个询问,每次询问树上所有点权在$[L, R]$范围内的点到$u$的距离之和,强制在线。
输入格式:
第一行三个用空格分开的数 n、Q和A,表示树的大小、开店的方案个数和妖怪的年龄上限。
第二行n个用空格分开的数 x_1、x_2、…、x_n,x_i 表示第i 个地点妖怪的年龄,满足0<=x_i<A。(年龄是可以为 0的,例如刚出生的妖怪的年龄为 0。)
接下来 n-1 行,每行三个用空格分开的数 a、b、c,表示树上的顶点 a 和 b 之间有一条权为c(1 <= c <= 1000)的边,a和b 是顶点编号。
接下来Q行,每行三个用空格分开的数 u、 a、 b。对于这 Q行的每一行,用 a、b、A计算出 L和R,表示询问“在地方 u开店,面向妖怪的年龄区间为[L,R]的方案的方便值是多少”。对于其中第 1 行,L 和 R 的计算方法为:L=min(a%A,b%A), R=max(a%A,b%A)。对于第 2到第 Q行,假设前一行得到的方便值为 ans,那么当
前行的 L 和 R 计算方法为: L=min((a+ans)%A,(b+ans)%A), R=max((a+ans)%A,(b+ans)%A)。
输出格式:
对于每个方案,输出一行表示方便值。
输入样例#1:
10 10 10 0 0 7 2 1 4 7 7 7 9 1 2 270 2 3 217 1 4 326 2 5 361 4 6 116 3 7 38 1 8 800 6 9 210 7 10 278 8 9 8 2 8 0 9 3 1 8 0 8 4 2 7 9 7 3 4 7 0 2 2 7 3 2 1 2 3 4
输出样例#1:
1603 957 7161 9466 3232 5223 1879 1669 1282 0
满足 n<=150000,Q<=200000。对于所有数据,满足 A<=10^9
我们使用边分治解决这个问题,因为树是二叉树,具有很优秀的性质,适合使用边分治。我们考虑在分治过程中记录下子分治结构所有点到中心边两端点的距离的集合,并以此计算权值下标的距离前缀和。我们将分治树的树形态保存下来,对于每个点显然对应着分治树的一个树链,对于树链上的每一个中心边,求出询问点对侧半棵树点权在区间内的点的距离之和,可以利用二分查找和之前的前缀和求出,这些点对答案的贡献=距离之和+点数*(中心边边权+询问点到该侧中心边端点的距离)。
简单地说就是用一个边分树优化了暴力查找的过程,使得查找涉及的点规模降至$O(\log n)$,因此每次查找的复杂度是$O(\log^2 n)$的,即总复杂度为$O(n \log^2 n)$。虽然这个复杂度与树链剖分是一致的,但是常数比树剖不知道大到哪去了,因此在洛谷上得开O2过,而且由于维护的信息多,相对空间占用也比树剖大。
以及,如果随机小数据没卵问题,随机大数据小概率出问题,那大概可能是因为维护信息用了个set,而set会去重,要改成multiset QAQ
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <queue>
#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 * 10 + c - '0';
return res * neg;
}
const int MAXN = 150005;
int n, q;
LL A, w[MAXN];
struct Edge {
int to;
LL w;
int nxt;
} gra[MAXN << 1];
int head[MAXN], tot;
inline void addedge(int u, int v, LL w) {
gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, w, head[v]}; head[v] = tot++;
}
bool del[MAXN];
int siz[MAXN], ct, ctsiz;
void dfs_size(int u, int fa) {
siz[u] = 1;
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(v == fa || del[i >> 1]) continue;
dfs_size(v, u);
siz[u] += siz[v];
}
}
void findct(int u, int fa, int tot) {
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(v == fa || del[i >> 1]) continue;
findct(v, u, tot);
int vsiz = std::max(siz[v], tot - siz[v]);
if(vsiz < ctsiz) {
ct = i; ctsiz = vsiz;
}
}
}
struct Info {
LL did, s, dis;
};
struct Info2 {
LL w, dis, cnt;
inline bool operator<(const Info2 &rhs) const {
return w < rhs.w;
}
inline bool operator<=(const Info2 &rhs) const {
return w <= rhs.w;
}
inline bool operator>(const Info2 &rhs) const {
return w > rhs.w;
}
inline bool operator>=(const Info2 &rhs) const {
return w >= rhs.w;
}
};
std::vector<Info> pinfo[MAXN];
typedef std::pair<LL, LL> PII;
std::priority_queue<PII, std::vector<PII>, std::greater<PII> > cinfom;
std::vector<Info2> cinfo[MAXN][2];
int eid[MAXN], ctot;
void dfs_dis(int u, int fa, LL dis, int did, int s) {
cinfom.push(PII(w[u], dis));
pinfo[u].push_back(Info {did, s, dis});
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(v == fa || del[i >> 1]) continue;
dfs_dis(v, u, dis + gra[i].w, did, s);
}
}
inline void work(int u, int s, int did) {
dfs_dis(u, 0, 0, did, s);
LL lst = -1, lid = 0;
cinfo[did][s].push_back(Info2 {-1, 0, 0});
while(!cinfom.empty()) {
if(cinfom.top().first != lst) {
cinfo[did][s].push_back(Info2 {cinfom.top().first, 0, 0});
lst = cinfom.top().first; lid++;
}
cinfo[did][s][lid].dis += cinfom.top().second;
cinfo[did][s][lid].cnt++;
cinfom.pop();
}
for(int i = 1; i < cinfo[did][s].size(); i++) {
cinfo[did][s][i].dis += cinfo[did][s][i - 1].dis;
cinfo[did][s][i].cnt += cinfo[did][s][i - 1].cnt;
}
}
void divide(int u) {
dfs_size(u, 0);
ct = -1; ctsiz = n; findct(u, 0, siz[u]);
if(ct == -1) return;
int x = gra[ct].to, y = gra[ct ^ 1].to, t = ++ctot;
del[ct >> 1] = true;
work(x, 0, t); work(y, 1, t);
eid[t] = ct;
divide(x); divide(y);
}
inline LL query(int u, LL l, LL r) {
LL res = 0;
for(int i = 0; i < pinfo[u].size(); i++) {
Info info = pinfo[u][i];
int s = info.s;
std::vector<Info2>::iterator
lit = std::lower_bound(cinfo[info.did][s ^ 1].begin(), cinfo[info.did][s ^ 1].end(), Info2 {l, 0, 0}) - 1,
rit = std::upper_bound(cinfo[info.did][s ^ 1].begin(), cinfo[info.did][s ^ 1].end(), Info2 {r, 0, 0}) - 1;
LL dis = rit->dis - lit->dis, cnt = rit->cnt - lit->cnt;
res += dis + 1ll * cnt * (gra[eid[info.did]].w + info.dis);
}
return res;
}
int main() {
memset(head, -1, sizeof(head));
n = readint(); q = readint(); A = readint();
for(int i = 1; i <= n; i++) {
w[i] = readint();
}
for(int i = 1, a, b, c; i < n; i++) {
a = readint(); b = readint(); c = readint();
addedge(a, b, c);
}
divide(1);
LL lastans = 0;
while(q--) {
LL u = readint(), a = readint(), b = readint(),
l = std::min((a + lastans) % A, (b + lastans) % A),
r = std::max((a + lastans) % A, (b + lastans) % A);
printf("%lld\n", lastans = query(u, l, r));
}
return 0;
}