嘟嘟嘟




好题,好题




刚开始突发奇想写了一个\(O(n ^ 2)\)暴力,结果竟然过了?!后来才知道是上传题的人把单个数据点开成了10s……

不过不得不说我这暴力写的挺好看的。删边模仿链表删边,加边的时候遍历其中一棵树,使两棵树染上相同的颜色,这样判联通就能达到\(O(1)\)了。

所以我决定先放一个暴力代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 5e4 + 5;
const int maxe = 2e5 + 5;
inline ll read()
{
ll ans = 0;
char ch = getchar(), last = ' ';
while(!isdigit(ch)) last = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(last == '-') ans = -ans;
return ans;
}
inline void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
} int n, m, a[maxn];
struct Edge
{
int nxt, to;
}e[maxe];
int head[maxn], ecnt = -1;
In void addEdge(int x, int y)
{
e[++ecnt] = (Edge){head[x], y};
head[x] = ecnt;
} In ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;} int fa[maxn], dep[maxn], col[maxn], Col = 0;
In void dfs(int now, int _f, int Col)
{
fa[now] = _f, dep[now] = dep[_f] + 1;
col[now] = Col;
for(int i = head[now], v; ~i; i = e[i].nxt)
{
if((v = e[i].to) == _f) continue;
dfs(v, now, Col);
}
} In bool cut(int x, int y)
{
for(int i = head[x], v, pre = 0; ~i; pre = i, i = e[i].nxt)
{
if((v = e[i].to) == y)
{
if(!pre) head[x] = e[i].nxt;
else e[pre].nxt = e[i].nxt;
return 1;
}
}
return 0;
}
In void Cut(int x, int y)
{
if(!cut(x, y)) return;
cut(y, x);
dfs(y, 0, ++Col);
}
In void Link(int x, int y)
{
if(col[x] == col[y]) return;
addEdge(x, y), addEdge(y, x);
dfs(y, x, col[x]);
}
In void add(int x, int y, int d)
{
if(col[x] ^ col[y]) return;
while(x ^ y)
{
if(dep[x] < dep[y]) swap(x, y);
a[x] += d; x = fa[x];
}
a[x] += d;
}
int l[maxn], r[maxn], cnt1 = 0, cnt2 = 0;
In void query(int x, int y)
{
if(col[x] ^ col[y]) {puts("-1"); return;}
cnt1 = cnt2 = 0;
while(x ^ y)
{
if(dep[x] >= dep[y]) l[++cnt1] = x, x = fa[x];
else r[++cnt2] = y, y = fa[y];
}
l[++cnt1] = x;
ll ret1 = 0, ret2 = (1LL * cnt1 + cnt2) * (cnt1 + cnt2 + 1) / 2;
for(int i = 1; i <= cnt1; ++i) ret1 += 1LL * a[l[i]] * i * (cnt1 + cnt2 - i + 1);
for(int i = 1; i <= cnt2; ++i) ret1 += 1LL * a[r[i]] * i * (cnt1 + cnt2 - i + 1);
ll Gcd = gcd(ret1, ret2);
write(ret1 / Gcd), putchar('/'), write(ret2 / Gcd), enter;
} int main()
{
Mem(head, -1);
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i < n; ++i)
{
int x = read(), y = read();
addEdge(x, y), addEdge(y, x);
}
dfs(1, 0, ++Col);
for(int i = 1; i <= m; ++i)
{
int op = read(), x = read(), y = read();
if(op == 1) Cut(x, y);
else if(op == 2) Link(x, y);
else if(op == 3)
{
int d = read();
add(x, y, d);
}
else query(x, y);
}
return 0;
}



好,那么我们切入正题。
不对,我先吐槽一下:第2和第4两个点我的LCT跑的比暴力还慢,然后别的点和暴力差不多,结果总时间竟然比暴力还慢……调了半天也不知道为啥,我这可活什么劲。


好,那现在真的切入正题了。
有时候维护LCT跟线段树差不多,比如这道题,核心就是pushdown和pushup怎么写。
线段树是连个子区间合并,那么这个LCT就是两条链首尾相连合并成一条链。


关于pushup,我实在写不动了,就扔出一篇博客:[城市旅行题解](https://www.luogu.org/blog/user25308/solution-p4842)
思路就是算出左子树的答案$ans_l$,左子树在整棵树中的贡献$w_l$,右子树同理,那么整棵树的答案就是$w_l + w_r = ans_l + \Delta x_l + ans_r + \Delta x_r$,其中两个$\Delta$是能手算出来的。


关于pushdown,除了期望,我和那篇题解都一样。因为我数学没那位老哥那么巨,小学也没学过奥数,推了一阵子搞出个这么个东西:$d * (\frac{n ^ 3 + 2n ^ 2 + n}{2} - \sum i ^ 2)$。
然后发现没办法$O(1)$求……
你以为我就去抄题解了吗?那不可能,别忘了,咱这是信竞,不是数竞,后面那个$\sum$直接预处理出来不就完了嘛。


对了,子树大小可能在运算的时候会爆int,别忘强制转换成long long。
```c++
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 5e4 + 5;
inline ll read()
{
ll ans = 0;
char ch = getchar(), last = ' ';
while(!isdigit(ch)) last = ch, ch = getchar();
while(isdigit(ch)) ans = (ans = 10) write(x / 10);
putchar(x % 10 + '0');
}

int n, m;

ll SUM[maxn];

struct Tree

{

int ch[2], fa, siz, rev;

ll val, lzy, sum, lsum, rsum, ans;

}t[maxn];

define ls t[now].ch[0]

define rs t[now].ch[1]

define S t[now].siz

In ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}

In void c_rev(int now)

{

swap(ls, rs); swap(t[now].lsum, t[now].rsum);

t[now].rev ^= 1;

}

In void c_add(int now, ll d)

{

t[now].lzy += d; t[now].val += d;

t[now].sum += d * S;

t[now].lsum += ((d * S * (S + 1)) >> 1);

t[now].rsum += ((d * S * (S + 1)) >> 1);

t[now].ans += d * (((1LL * S * S * S + 1LL * S * (S << 1) + S) >> 1) - SUM[S]);

}

In void pushdown(int now)

{

if(t[now].rev)

{

if(ls) c_rev(ls);

if(rs) c_rev(rs);

t[now].rev = 0;

}

if(t[now].lzy)

{

if(ls) c_add(ls, t[now].lzy);

if(rs) c_add(rs, t[now].lzy);

t[now].lzy = 0;

}

}

In void pushup(int now)

{

t[now].siz = t[ls].siz + t[rs].siz + 1;

t[now].sum = t[ls].sum + t[rs].sum + t[now].val;

t[now].lsum = t[ls].lsum + t[rs].lsum + (t[rs].sum + t[now].val) * (t[ls].siz + 1);

t[now].rsum = t[rs].rsum + t[ls].rsum + (t[ls].sum + t[now].val) * (t[rs].siz + 1);

t[now].ans = t[ls].ans + t[rs].ans + t[ls].lsum * (t[rs].siz + 1) + t[rs].rsum * (t[ls].siz + 1) + t[now].val * (t[ls].siz + 1) * (t[rs].siz + 1);

}

In bool n_root(int x)

{

return t[t[x].fa].ch[0] == x || t[t[x].fa].ch[1] == x;

}

In void rotate(int x)

{

int y = t[x].fa, z = t[y].fa, k = (t[y].ch[1] == x);

if(n_root(y)) t[z].ch[t[z].ch[1] == y] = x; t[x].fa = z;

t[y].ch[k] = t[x].ch[k ^ 1], t[t[y].ch[k]].fa = y;

t[x].ch[k ^ 1] = y, t[y].fa = x;

pushup(y), pushup(x);

}

int st[maxn], top = 0;

In void splay(int x)

{

int y = x; st[top = 1] = y;

while(n_root(y)) st[++top] = y = t[y].fa;

while(top) pushdown(st[top--]);

while(n_root(x))

{

int y = t[x].fa, z = t[y].fa;

if(n_root(y)) rotate(((t[y].ch[0] == x) ^ (t[z].ch[0] == y)) ? x : y);

rotate(x);

}

}

In void access(int x)

{

int y = 0;

while(x)

{

splay(x); t[x].ch[1] = y;

pushup(x);

y = x; x = t[x].fa;

}

}

In void make_root(int x)

{

access(x), splay(x);

c_rev(x);

}

In int find_root(int x)

{

access(x), splay(x);

while(t[x].ch[0]) pushdown(x), x = t[x].ch[0];

return x;

}

In void split(int x, int y)

{

make_root(x);

access(y), splay(y);

}

In void Link(int x, int y)

{

make_root(x);

if(find_root(y) ^ x) t[x].fa = y;

}

In void Cut(int x, int y)

{

make_root(x);

if(find_root(y) == x && t[x].fa == y && !t[x].ch[1])

t[y].ch[0] = t[x].fa = 0, pushup(y);

}

In void update(int x, int y, int d)

{

make_root(x);

if(find_root(y) ^ x) return;

split(x, y); c_add(y, d);

pushup(y);

}

In void query(int x, int y)

{

make_root(x);

if(find_root(y) ^ x) {puts("-1"); return;}

split(x, y);

ll Siz = t[y].siz, tp = (Siz * (Siz + 1)) >> 1, Gcd = gcd(t[y].ans, tp);

write(t[y].ans / Gcd), putchar('/'), write(tp / Gcd), enter;

}

int main()

{

n = read(), m = read();

for(int i = 1; i <= n; ++i)

{

t[i].val = read(), t[i].siz = 1;

t[i].sum = t[i].lsum = t[i].rsum = t[i].ans = t[i].val;

SUM[i] = SUM[i - 1] + i * i;

}

for(int i = 1; i < n; ++i)

{

int x = read(), y = read();

Link(x, y);

}

for(int i = 1; i <= m; ++i)

{

int op = read(), x = read(), y = read();

if(op == 1) Cut(x, y);

else if(op == 2) Link(x, y);

else if(op == 4) query(x, y);

else

{

int d = read();

update(x, y, d);

}

}

return 0;

}

luogu P4842 城市旅行的更多相关文章

  1. P4842 城市旅行

    题目链接 题意分析 首先存在树上的删边连边操作 所以我们使用\(LCT\)维护 然后考虑怎么维护答案 可以发现 对于一条链 我们编号为\(1,2,3,...,n\) 那么期望就是 \[\frac{a_ ...

  2. [luogu P3313] [SDOI2014]旅行

    [luogu P3313] [SDOI2014]旅行 题目描述 S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,满足从一个城市出发可以到达其它所有城市.每个城市信仰不同的宗教,如飞天面条神 ...

  3. 【LCT】BZOJ3091 城市旅行

    3091: 城市旅行 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1927  Solved: 631[Submit][Status][Discuss ...

  4. BZOJ 3091: 城市旅行 [LCT splay 期望]

    3091: 城市旅行 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1454  Solved: 483[Submit][Status][Discuss ...

  5. luogu P1401 城市

    题目链接 luogu P1401 城市 题解 二分最小边权,dinic检验 代码 // luogu-judger-enable-o2 /* 二分最小边权,dinic检验 */ #include< ...

  6. luogu P2134 百日旅行

    题目链接 luogu P2134 百日旅行 题解 dp方程好想吧 优化有些玄学惹 不会证.... 不过我会三分和贪心 \滑稽 但还是写dp吧 代码 #include<cstdio> #in ...

  7. 【BZOJ3091】城市旅行 LCT

    [BZOJ3091]城市旅行 Description Input Output Sample Input 4 5 1 3 2 5 1 2 1 3 2 4 4 2 4 1 2 4 2 3 4 3 1 4 ...

  8. 【Luogu】P3313旅行(树链剖分)

    题目链接 动态开点的树链剖分qwq. 跟小奇的花园一模一样,不做过多讲解. #include<cstdio> #include<cstring> #include<cct ...

  9. Luogu P1401 城市(二分+网络流)

    P1401 城市 题意 题目描述 N(2<=n<=200)个城市,M(1<=m<=40000)条无向边,你要找T(1<=T<=200)条从城市1到城市N的路,使得最 ...

随机推荐

  1. DataRead和DataSet的异同

    第一种解释 DataReader和DataSet最大的区别在于,DataReader使用时始终占用SqlConnection(俗称:非断开式连接),在线操作数据库时,任何对SqlConnection的 ...

  2. SpringCloud Feign的分析

    Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单.我们只需要使用Feign来创建一个接口并用注解来配置它既可完成. @FeignClient(v ...

  3. 大数据技术之_19_Spark学习_01_Spark 基础解析 + Spark 概述 + Spark 集群安装 + 执行 Spark 程序

    第1章 Spark 概述1.1 什么是 Spark1.2 Spark 特点1.3 Spark 的用户和用途第2章 Spark 集群安装2.1 集群角色2.2 机器准备2.3 下载 Spark 安装包2 ...

  4. 为容器化的 Go 程序搭建 CI

    本文介绍如何使用 Jenkins 的声明式 pipeline 为一个简单的 Golang web 应用搭建 CI 环境.如果你还不太了解 Jenkins 及其声明式 pipeline,请先参考笔者的 ...

  5. 痞子衡嵌入式:飞思卡尔Kinetis开发板OpenSDA调试器那些事(上)- 背景与架构

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔Kinetis MCU开发板板载OpenSDA调试器(上篇). 众所周知,嵌入式软件开发几乎离不开调试器,因为写一个稍有代码规模 ...

  6. python基础6--面向对象基础、装饰器

    1.类 class Student: def __init__(self, name, grade): self.name = name self.grade = grade def introduc ...

  7. C#如何生成缩略图、水印

    1.安装CodeCarvings.Piczard   Install-Package CodeCarvings.Piczard 2.生成缩略图 ImageProcessingJob jobThumb ...

  8. C# ListBox实现显示插入最新的数据的方法

    在我们使用ListBox控件时,如果我们在里面不断的添加一条条数据,但是在我们添加的数据过多超过了ListBox显示的窗口时(此时会产生滑动条), 发现我们无法看到最新添加的数据.实现倒序显示此处有两 ...

  9. Maven(十四)Maven 继承

    以Junit为例 由于junit的依赖的范围为test,所以在每一个项目中都必须配置一个junit. 为了统一管理方便,可以单独创建一个项目用来进行**统一管理**junit的版本 即在子项目中不设置 ...

  10. python爬虫项目(scrapy-redis分布式爬取房天下租房信息)

    python爬虫scrapy项目(二) 爬取目标:房天下全国租房信息网站(起始url:http://zu.fang.com/cities.aspx) 爬取内容:城市:名字:出租方式:价格:户型:面积: ...