luogu P4842 城市旅行
嘟嘟嘟
好题,好题
刚开始突发奇想写了一个\(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 城市旅行的更多相关文章
- P4842 城市旅行
题目链接 题意分析 首先存在树上的删边连边操作 所以我们使用\(LCT\)维护 然后考虑怎么维护答案 可以发现 对于一条链 我们编号为\(1,2,3,...,n\) 那么期望就是 \[\frac{a_ ...
- [luogu P3313] [SDOI2014]旅行
[luogu P3313] [SDOI2014]旅行 题目描述 S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,满足从一个城市出发可以到达其它所有城市.每个城市信仰不同的宗教,如飞天面条神 ...
- 【LCT】BZOJ3091 城市旅行
3091: 城市旅行 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1927 Solved: 631[Submit][Status][Discuss ...
- BZOJ 3091: 城市旅行 [LCT splay 期望]
3091: 城市旅行 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1454 Solved: 483[Submit][Status][Discuss ...
- luogu P1401 城市
题目链接 luogu P1401 城市 题解 二分最小边权,dinic检验 代码 // luogu-judger-enable-o2 /* 二分最小边权,dinic检验 */ #include< ...
- luogu P2134 百日旅行
题目链接 luogu P2134 百日旅行 题解 dp方程好想吧 优化有些玄学惹 不会证.... 不过我会三分和贪心 \滑稽 但还是写dp吧 代码 #include<cstdio> #in ...
- 【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 ...
- 【Luogu】P3313旅行(树链剖分)
题目链接 动态开点的树链剖分qwq. 跟小奇的花园一模一样,不做过多讲解. #include<cstdio> #include<cstring> #include<cct ...
- Luogu P1401 城市(二分+网络流)
P1401 城市 题意 题目描述 N(2<=n<=200)个城市,M(1<=m<=40000)条无向边,你要找T(1<=T<=200)条从城市1到城市N的路,使得最 ...
随机推荐
- RabbitMQ消息队列(十)-高可用集群部署实战
前几章讲到RabbitMQ单主机模式的搭建和使用,我们在实际生产环境中出于对性能还有可用性的考虑会采用集群的模式来部署RabbitMQ. RabbitMQ集群基本概念 Rabbit模式大概分为以下三种 ...
- 如何用sysbench做好IO性能测试
sysbench 是一个非常经典的综合性能测试工具,通常都用它来做数据库的性能压测,但也可以用来做CPU,IO的性能测试.而对于IO测试,不是很推荐sysbench,倒不是说它有错误,工具本身没有任何 ...
- mysql更改数据存储目录
具体操作请参考文章 1.http://blog.csdn.net/aaronbai/article/details/1431190 更改数据存储目录时候会出现 ERROR 2002 (HY000): ...
- Spring Boot(十四)RabbitMQ延迟队列
一.前言 延迟队列的使用场景:1.未按时支付的订单,30分钟过期之后取消订单:2.给活跃度比较低的用户间隔N天之后推送消息,提高活跃度:3.过1分钟给新注册会员的用户,发送注册邮件等. 实现延迟队列的 ...
- k8s架构分析(二)--技术流ken
master节点 k8s的集群由master和node组成,节点上运行着若干k8s服务. master节点之上运行着的后台服务有kube-apiserver .kube-scheduler.kube- ...
- 第9章 使用客户端凭据保护API - Identity Server 4 中文文档(v1.0.0)
快速入门介绍了使用IdentityServer保护API的最基本方案. 我们将定义一个API和一个想要访问它的客户端. 客户端将通过提供ClientCredentials在IdentityServer ...
- c#基础,单线程,跨线程访问和线程带参数
using System; using System.Collections.Generic; using System.Threading; using System.Windows.Forms; ...
- 命令行BASH的基本操作
前面说了,我们要尽量少用GNOME图形界面,而应该以使用BASH命令行为主. SHELL Shell是操作系统内核的壳,因为我们不能直接操作系统的内核Kernel,只能通过Shell去操作,Shell ...
- iview-admin框架运行步骤
第一步: 前往github下载整个iview-admin框架的全部源码 github地址: https://github.com/iview/iview-admin 第二步: 点击Clone or d ...
- laravel项目thinksns-plus安装出现RuntimeException Symlink from * to * failed错误
今天xshell安装thinksns-plus的laravel项目时出现了一个错误, [RuntimeException] Symlink from "/root/www.z5w.net/t ...