嘟嘟嘟




好题,好题




刚开始突发奇想写了一个\(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. 带着萌新看springboot源码

    springboot的功能确实强悍,只需要很少的配置,就能够做出来一个简单的web应用,下面我就简要的分析一下为什么springboot能够起作用. 不觉得很奇怪吗?只需要一个主配置类(就是启动那个m ...

  2. Chapter 5 Blood Type——14

    "You're wrong." His voice was almost inaudible. “你错了.” 他的声音几乎听不见 He looked down, stealing ...

  3. 补习系列(5)-springboot- restful应用

    一.目标 了解 Restful 是什么,基本概念及风格: 能使用SpringBoot 实现一套基础的 Restful 风格接口: 利用Swagger 生成清晰的接口文档. 二.Restful 入门 什 ...

  4. Nginx的正向代理与反向代理详解

    正向代理和反向代理的概念 代理服务(Proxy),通常也称为正向代理服务. 如果把局域网外Internet想象成一个巨大的资源库,那么资源就分布到了Internet的各个点上,局域网内的客户端要访问这 ...

  5. Ansible 入门指南 - 安装及 Ad-Hoc 命令使用

    安装及配置 ansible Ansilbe 管理员节点和远程主机节点通过 SSH 协议进行通信.所以 Ansible 配置的时候只需要保证从 Ansible 管理节点通过 SSH 能够连接到被管理的远 ...

  6. SpringCloud系列——Zuul 动态路由

    前言 Zuul 是在Spring Cloud Netflix平台上提供动态路由,监控,弹性,安全等边缘服务的框架,是Netflix基于jvm的路由器和服务器端负载均衡器,相当于是设备和 Netflix ...

  7. JQuery autocomplete获得焦点触发弹出下拉框

    需求:autocomplete控件,当点击获得焦点的时候也要弹出下拉列表(autocomplete默认是输入之后才会跟随出下拉列表),下面直接贴代码. js代码: $("#customerN ...

  8. DOM编程以及domReady加载的几种方式

    1,关于DOM编程       DOM编程主要是对dom树节点进行操作,所以你必须掌握基本的节点类型,如何去获取节点名字以及值(这些相关知识你可以去网上查,这里推荐一个慕课学习网站->https ...

  9. csharp: LocalDataCache.sync

    app.config: <?xml version="1.0" encoding="utf-8" ?> <configuration> ...

  10. zabbix3.4 监控mysql 数据库连接数

    zabbix3.4 监控mysql 数据库连接数具体监控配置待定,近期即将发布!