Loj #2553. 「CTSC2018」暴力写挂

Loj #2553. 「CTSC2018」暴力写挂

题目描述

temporaryDO 是一个很菜的 OIer 。在 4 月,他在省队选拔赛的考场上见到了《林克卡特树》一题,其中 \(k = 0\) 的部分分是求树 \(T\) 上的最长链。可怜的 temporaryDO 并不会做这道题,他在考场上抓猫耳挠猫腮都想不出一点思路。

这时,善良的板板出现在了空中,他的身上发出璀璨却柔和的光芒,荡漾在考场上。‘‘题目并不难。’’ 板板说。那充满磁性的声音,让 temporaryDO 全身充满了力量。

他决定:写一个枚举点对求 LCA 算距离的 \(k = 0\) 的 \(O(n^2\log\ n)\) 的部分分程序!于是, temporaryDO 选择以 \(1\) 为根,建立了求 LCA 的树链剖分结构,然后写了二重 for 循环枚举点对。

然而,菜菜的 temporaryDO 不小心开小了数组,于是数组越界到了一片神秘的内存区域。但恰好的是,那片内存区域存储的区域恰好是另一棵树 \(T′\) 。这样一来,程序并没有 RE ,但他求 \(x\) 和 \(y\) 的距离的时候,计算的是

\[depth(x) + depth(y) - (depth(LCA(x , y)) + depth′ (LCA′ (x, y)))\]

最后程序会输出每一对点对 \(i, j (i \le j)\) 的如上定义的‘‘距离’’ 的最大值。

temporaryDO 的程序在评测时光荣地爆零了。但他并不服气,他决定花好几天把自己的程序跑出来。请你根据 \(T\) 和 \(T′\) 帮帮可怜的 temporaryDO 求出他程序的输出。

输入格式

第一行包含一个整数 \(n\) ,表示树上的节点个数;

第 \(2\) 到第 \(n\) 行,每行三个整数 \(x , y , v\) ,表示 \(T\) 中存在一条从 \(x\) 到 \(y\) 的边,其长度为 \(v\) ;

第 \(n + 1\) 到第 \(2n - 1 行\) ,每行三个整数 \(x , y , v\) ,表示 \(T′\) 中存在一条从 \(x\) 到 \(y\) 的边,其长度为 \(v\) 。

输出格式

输出一行一个整数,表示 temporaryDO 的程序的输出。

数据范围与提示

对于所有数据, \(n \le 366666 , |v| \le 2017011328\) 。

以前一直觉得边分治和点分治没什么区别,做了这道题才发现我太naive了。

首先题目中给的式子,不好看,所以我们把它变一下形:
\[
\begin{align}
&dep_x+dep_y-dep_{lca(x,y)}
\\&=\frac{1}{2}(2*dep_x+dep_y-2*dep_{lca(x,y)})
\\&=\frac{1}{2}(dep_x+dep_y+dis_{x,y})
\end{align}
\]

\[
ans=\frac{1}{2}(dep_x+dep_y+dis_{x,y}-dep'_{lca'(x,y)})
\]

于是我们可以枚举第二颗树的\(lca\),然后计算其子树之间的\(dep_x+dep_y+dis_{x,y}\)的最大值。后面部分就可以用边分治来维护。

可以类比点分治来理解边分治,就是在每个分治连通块中找到一条边使得边两端的连通块大小尽量平均。但是我们发现,一个菊花就可以把这个分治卡死。原因是某个点的度数太大了。于是我们考虑转成二叉树。具体就是每个点的上面连一个额外点,然后一个父亲节点连向其中一个儿子的额外点,几个儿子的额外点再连成一条线(代码一看就懂)。

假设分治中心边是\((x,y)\),我们像点分治那样统计分支块内每个点\(p\)到分治中心边的其中一个点的距离(\(dep_p+dis_{x,p}\))。这个距离有两种方向,分别对应\(x,y\)所在的连通块。我们发现,边分治的分治树是棵二叉树。所以对于每个点,我们可以开个二叉树,记录其在每一个分治连通块内的到中心点的距离。

然后就可以算答案了。当枚举了第二棵树上的\(lca\)的时候,合并每个子树的二叉树,同一种节点(到根路径相同)代表同一个分支连通块,我们可以在合并过程中更新答案。

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 800005

using namespace std;
inline ll Get() {ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

int n;
struct graph {
    int to[N<<2],nxt[N<<2],dis[N<<2];
    int h[N<<1],cnt=1;
    void add(int i,int j,int d) {
        to[++cnt]=j;
        nxt[cnt]=h[i];
        dis[cnt]=d;
        h[i]=cnt;
    }
    void Init() {
        memset(h,0,sizeof(h));
        cnt=1;
    }
}s,g;

int vertex;
ll dep[N];
void build_edge(int v,int fr) {
    int lst=v;
    for(int i=s.h[v];i;i=s.nxt[i]) {
        int to=s.to[i];
        if(to==fr) continue ;
        vertex++;
        g.add(lst,vertex,0);
        g.add(vertex,lst,0);
        g.add(vertex,to,s.dis[i]);
        g.add(to,vertex,s.dis[i]);
        lst=vertex;
        dep[to]=dep[v]+s.dis[i];
        build_edge(to,v);
    }
}

int size[N<<1];
int sum,E,mx;
bool vis[N<<1];

void Find_edge(int v,int fr) {
    size[v]=1;
    for(int i=g.h[v];i;i=g.nxt[i]) {
        int to=g.to[i];
        if(vis[i]||to==fr) continue ;
        Find_edge(to,v);
        size[v]+=size[to];
        int now=max(size[to],sum-size[to]);
        if(mx>now) {
            mx=now;
            E=i;
        }
    }
}

struct node {
    ll dis;
    int dir;
    node() {}
    node(ll _dis,int _dir) {dis=_dis,dir=_dir;}
};

vector<node>st[N<<1];
void statis(int v,int fr,ll dis,int dir) {
    size[v]=1;
    if(v<=n) {
        st[v].push_back(node(dep[v]+dis,dir));
    }
    for(int i=g.h[v];i;i=g.nxt[i]) {
        int to=g.to[i];
        if(to==fr||vis[i]) continue ;
        statis(to,v,dis+g.dis[i],dir);
        size[v]+=size[to];
    }
}

void solve(int v) {
    sum=size[v];
    mx=1e9;
    Find_edge(v,0);
    int x=g.to[E],y=g.to[E^1];
    vis[E]=vis[E^1]=1;
    statis(x,0,0,0),statis(y,0,g.dis[E],1);
    if(size[x]>1) solve(x);
    if(size[y]>1) solve(y);
}

int rt[N<<1];
int ls[N*20],rs[N*20];
ll lmx[N*20],rmx[N*20];
int tot;
void build_tree(int &v,vector<node>&a,int now) {
    if(now==a.size()) return ;
    v=++tot;
    if(a[now].dir==0) {
        lmx[v]=a[now].dis;
        rmx[v]=-1ll<<60;
        build_tree(ls[v],a,now+1);
    } else {
        rmx[v]=a[now].dis;
        lmx[v]=-1ll<<60;
        build_tree(rs[v],a,now+1);
    }
}

ll ans=-1ll<<60;
ll Dis;
int Merge(int a,int b) {
    if(!a||!b) return a+b;
    ans=max(ans,max(lmx[a]+rmx[b],lmx[b]+rmx[a])-2*Dis);
    lmx[a]=max(lmx[a],lmx[b]);
    rmx[a]=max(rmx[a],rmx[b]);
    ls[a]=Merge(ls[a],ls[b]);
    rs[a]=Merge(rs[a],rs[b]);
    return a;
}

void dfs2(int v,int fr,ll dis) {
    ans=max(ans,2*dep[v]-2*dis);
    for(int i=s.h[v];i;i=s.nxt[i]) {
        int to=s.to[i];
        if(to==fr) continue ;
        dfs2(to,v,dis+s.dis[i]);
        Dis=dis;
        rt[v]=Merge(rt[v],rt[to]);
    }
}

int main() {
    n=Get();
    for(int i=1;i<n;i++) {
        int a=Get(),b=Get(),c=Get();
        s.add(a,b,c),s.add(b,a,c);
    }
    vertex=n;
    build_edge(1,0);
    size[1]=vertex;
    solve(1);
    for(int i=1;i<=n;i++) {
        build_tree(rt[i],st[i],0);
    }
    s.Init();
    for(int i=1;i<n;i++) {
        int a=Get(),b=Get(),c=Get();
        s.add(a,b,c),s.add(b,a,c);
    }
    dfs2(1,0,0);
    cout<<ans/2;
    return 0;
}

原文地址:https://www.cnblogs.com/hchhch233/p/10987737.html

时间: 2024-10-12 06:59:10

Loj #2553. 「CTSC2018」暴力写挂的相关文章

loj#2552. 「CTSC2018」假面

题目链接 loj#2552. 「CTSC2018」假面 题解 本题严谨的证明了我菜的本质 对于砍人的操作好做找龙哥就好了,blood很少,每次暴力维护一下 对于操作1 设\(a_i\)为第i个人存活的概率,\(d_i\)为死掉的概率,\(g_{i,j}\)是除i以外活了j个人的概率 那个选中i人的答案就是 \[a_i\times\sum_{j = 0} ^{k - 1}\frac{g_{i,j}}{j + 1}\] 对于\(g_{i,j}\) ,设\(f_{i,j}\)表示前\(i\)个人有\(

Loj #2554. 「CTSC2018」青蕈领主

Loj #2554. 「CTSC2018」青蕈领主 题目描述 "也许,我的生命也已经如同风中残烛了吧."小绿如是说. 小绿同学因为微积分这门课,对"连续"这一概念产生了浓厚的兴趣.小绿打算把连续的概念放到由整数构成的序列上,他定义一个长度为 \(m\) 的整数序列是连续的,当且仅当这个序列中的最大值与最小值的差,不超过\(m-1\).例如 \(\{1,3,2\}\) 是连续的,而 \(\{1,3\}\) 不是连续的. 某天,小绿的顶头上司板老大,给了小绿 \(T\)

uoj#400. 【CTSC2018】暴力写挂(边分治)

传送门 做一道题学一堆东西.jpg 猫老师的题--暴力拿的分好像比打挂的正解多很多啊--我纯暴力+部分分已经能有80了--正解没调对之前一直只有10分→_→ 先说一下什么是边分治.这个其实类似于点分治,不过分治对象从点换成边了,就是每次找到一条边,使其断开之后的两个连通块中最大的最小 于是我们就可以--等会儿如果在菊花图上怎么办?不是得卡到\(O(n^2)\)了? 不难发现这个东西的复杂度和节点的度数有关,于是为了假装这个东西能用避免这些情况,我们要把图给重构喽 简单来说就是通过加入虚点,把图给

UOJ#400. 【CTSC2018】暴力写挂 边分治 线段树合并

原文链接 www.cnblogs.com/zhouzhendong/p/UOJ400.html 前言 老年选手没有码力. 题解 先对第一棵树进行边分治,然后,设点 x 到分治中心的距离为 $D[x]$,点 x 在原树上的深度为 $d[x]$,那么 $$d[x]+d[y] - d[LCA(x,y)] - d'[LCA(x,y)] = \frac 12(D[x] + d[x]) + \frac 12 (D[y] + d[y]) - d'[LCA(x,y)]$$ 于是我们考虑将分治区域内的节点在第二棵

CTSC2018 暴力写挂

CTSC2018 暴力写挂 题意: 题目传送门 题解: emm--第一次写边分治-- 考虑到第二棵树上的\(Lca\)我们难以处理,于是我们可以考虑枚举第二棵树上的\(Lca\),然后在第一棵树上最大化\(dep_u + dep_v - dep_{lca}\).但是在这个式子中,又受到了第一棵树上\(Lca\)的限制,于是我们考虑化简式子.稍微化简一下会发现式子变成了\(\frac{1}{2} * (dep_u + dep_v + dis_{u, v})\),然后我们就可以将其转化成无根树来做了

[CTSC2018]暴力写挂——边分树合并

[CTSC2018]暴力写挂 题面不错 给定两棵树,两点“距离”定义为:二者深度相加,减去两棵树上的LCA的深度(深度指到根节点的距离) 求最大的距离. 解决多棵树的问题就是降维了. 经典的做法是边分树合并. 边分树结构类似0/1 trie 就是把边分树对于每个点拆开路径 合并两棵边分树同时可以得到两个边分树之间点对的路径的信息 感觉有点类似线段树合并. 根据“猫树”思想,两点间的路径一定经过边分树上LCA的那条边.(u,v不相等) 我们考虑在这个LCA处统计贡献 具体地,先对1树进行边分治 每

#2552. 「CTSC2018」假面

2552. 「CTSC2018」假面 一道"普及难度"DP题...然而考场上没想出来. 一堆人题解里说"只要会期望和逆元都能AC",我ssfd 还是在看完题解之后照着题解打的 大概就是设$f[i][j]$表示第$i$个人血量为$j$的概率 然而1号操作的转移就是$f[i][j]\leftarrow f[i][j](1-p)+f[i][j+1]p$. 二号操作就比较麻烦了. 可以枚举每个人,设$g[i]$为存活$i$个人的概率(不包括枚举的人),那么转移就是$g[i]

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

目录 题目链接 题解 代码 题目链接 loj#2076. 「JSOI2016」炸弹攻击 题解 模拟退火 退火时,由于答案比较小,但是温度比较高 所以在算exp时最好把相差的点数乘以一个常数让选取更差的的概率降低 代码 #include<ctime> #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define gc getchar() #define

Loj #2541「PKUWC2018」猎人杀

Loj #2541. 「PKUWC2018」猎人杀 题目链接 好巧妙的题! 游戏过程中,概率的分母一直在变化,所以就非常的不可做. 所以我们将问题转化一下:我们可以重复选择相同的猎人,只不过在一个猎人被选择了过后我们就给他打上标记,再次选择他的时候就无效.这样与原问题是等价的. 证明: 设\(sum=\sum_iw_i,kill=\sum_{i被杀死了}w_i\). 攻击到未被杀死的猎人\(i\)的概率为\(P\). 则根据题意\(P=\frac{w_i}{sum-kill}\). 问题转化后: