[CCC2007]Road Construction 题解
题目地址:POJ:3352 — Road Construction 题目描述 …
May all the beauty be blessed.
题目地址:洛谷:【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;
}
题目地址:洛谷:【P4219】[BJOI2014]大融合 – 洛谷、BZOJ:Problem 4530. — [Bjoi2014]大融合
小强要在N个孤立的星球上建立起一套通信系统。这套通信系统就是连接N个点的一个树。这个树的边是一条一条添加上去的。在某个时刻,一条边的负载就是它所在的当前能够联通的树上路过它的简单路径的数量。
例如,在上图中,现在一共有了5条边。其中,(3,8)这条边的负载是6,因为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8)。
现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的询问。
输入格式:
第一行包含两个整数N,Q,表示星球的数量和操作的数量。星球从1开始编号。
接下来的Q行,每行是如下两种格式之一:
A x y 表示在x和y之间连一条边。保证之前x和y是不联通的。
Q x y 表示询问(x,y)这条边上的负载。保证x和y之间有一条边。
1≤N,Q≤100000
输出格式:
对每个查询操作,输出被查询的边的负载。
输入样例#1:
8 6 A 2 3 A 3 4 A 3 8 A 8 7 A 6 5 Q 3 8
输出样例#1:
6
子树信息LCT的写法,对于每个点记录一个虚子树的大小,然后在access和link的时候维护一下轻重交替的信息就好。
复杂度$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 << 1) + (res << 3) + c - '0';
return res * neg;
}
inline char readsingle() {
char c;
while(!isalpha(c = fgc())) {}
return c;
}
const int MAXN = 100005;
struct Node {
int ch[2], fa, tsiz, siz;
bool rev;
} tr[MAXN];
inline void update(int p) {
tr[p].siz = tr[tr[p].ch[0]].siz + tr[tr[p].ch[1]].siz + tr[p].tsiz + 1;
}
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 reverse(int p) {
tr[p].rev ^= 1;
std::swap(tr[p].ch[0], tr[p].ch[1]);
}
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;
}
}
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].tsiz += tr[tr[p].ch[1]].siz;
tr[p].tsiz -= tr[q].siz;
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) {
split(u, v); tr[u].fa = v; tr[v].tsiz += tr[u].siz; update(v);
}
inline void cut(int u, int v) {
split(u, v);
if(tr[v].ch[0] != u || tr[u].ch[1]) return;
tr[v].ch[0] = tr[u].fa = 0;
}
int n, q;
int main() {
n = readint(); q = readint();
while(q--) {
char op = readsingle();
int x = readint(), y = readint();
if(op == 'A') {
link(x, y);
} else {
split(x, y);
printf("%d\n", tr[x].siz * (tr[y].siz - tr[x].siz));
}
}
return 0;
}