启发式合并复习

T1 永无乡

初做:2017.3.8

http://www.cnblogs.com/TheRoadToTheGold/p/6520714.html

现在:2017.3.30

大约2个半小时

许多点,点有点权,2个操作:连边、询问与某个点相连的点权的k值

#include<cstdio>
#include<algorithm>
#define N 100001
using namespace std;
int n,m,tot,cnt,fa[N];
int sum[N*20],lc[N*20],rc[N*20],root[N];
int key[N],hashh[N];
int find(int i) { return fa[i]==i ? i: fa[i]=find(fa[i]); }
void insert(int & now,int l,int r,int w)
{
    if(!now) now=++cnt;
    sum[now]++;
    if(l==r) return;
    int mid=l+r>>1;
    if(w<=mid) insert(lc[now],l,mid,w);
    else insert(rc[now],mid+1,r,w);
}
int query(int now,int l,int r,int w)
{
    if(l==r) return hashh[l];
    int mid=l+r>>1;
    if(w<=sum[lc[now]]) return query(lc[now],l,mid,w);
    else return query(rc[now],mid+1,r,w-sum[lc[now]]);
}
int unionn(int x,int y)
{
    if(!x) return y;
    if(!y) return x;
    lc[x]=unionn(lc[x],lc[y]);
    rc[x]=unionn(rc[x],rc[y]);
    sum[x]=sum[lc[x]]+sum[rc[x]];
    return x;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&key[i]);
        fa[i]=i;
        hashh[key[i]]=i;
    }
    int x,y,r1,r2;
    while(m--)
    {
        scanf("%d%d",&x,&y);
        r1=find(x);r2=find(y);
        if(r1!=r2) fa[r2]=r1;
    }
    for(int i=1;i<=n;i++)
     insert(root[find(i)],1,n,key[i]);
    scanf("%d",&m);
    char c[1];
    while(m--)
    {
        scanf("%s%d%d",c,&x,&y);
        if(c[0]==‘B‘)
        {
            r1=find(x);r2=find(y);
            if(r1!=r2)
            {
                fa[r2]=r1;
                unionn(root[r1],root[r2]);
            }
        }
        else
        {
            x=find(x);
            if(y>sum[root[x]]) printf("-1\n");
            else printf("%d\n",query(root[x],1,n,y));
        }
    }
}

首先,自恃做过一遍不读完题,上来写了一个离散化点权

题目中写着点权1——n,所以不用离散化

顺手写的错误:

if(r1!=r2)
{
    fa[r2]=r1;
    unionn(r1,r2);
}

应该是

    unionn(root[r1],root[r2]);

并查集写太熟了,以后注意思考

本质错误:合并

错误合并1:

void unionn(int & x,int y)
{
    if(!y) return;
    if(!x) x=++cnt;
    sum[x]+=sum[y];
    unionn(lc[x],lc[y]);
    unionn(rc[x],rc[y]);
}

把y合并到x上,如果y为0,那么自y一下都不用合并,

如果x为0,建立新的x

分析:合并思路正确,代码也正确,但做了大量无用的合并

如果x为0,新建一系列节点,这些节点完全等于y以下的节点,所以与y公用即可

错误合并2:

void unionn(int & x,int y)
{
    if(!y) return;
    if(!x) x=y;
    else sum[x]+=sum[y];
    unionn(lc[x],lc[y]);
    unionn(rc[x],rc[y]);
}

思路错误,错误原因还不清楚

正确合并:

int unionn(int x,int y)
{
    if(!x) return y;
    if(!y) return x;
    lc[x]=unionn(lc[x],lc[y]);
    rc[x]=unionn(rc[x],rc[y]);
    sum[x]=sum[lc[x]]+sum[rc[x]];
    return x;
}

自底向上的合并,公用节点

恕我无能,splay的没调出来

时间: 2024-10-12 17:43:50

启发式合并复习的相关文章

bzoj2733: [HNOI2012]永无乡(splay+启发式合并/线段树合并)

这题之前写过线段树合并,今天复习Splay的时候想起这题,打算写一次Splay+启发式合并. 好爽!!! 写了长长的代码(其实也不长),只凭着下午的一点记忆(没背板子...),调了好久好久,过了样例,submit,1A! 哇真的舒服 调试输出懒得删了QwQ #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<queue> #include

【Splay】【启发式合并】hdu6133 Army Formations

题意:给你一颗树,每个结点的儿子数不超过2.每个结点有一个权值,一个结点的代价被定义为将其子树中所有结点的权值放入数组排序后,每个权值乘以其下标的和.让你计算所有结点的代价. 二叉树的条件没有用到. 每个结点开一个Splay,从叶子往上启发式合并上去,可以先bfs一遍确定合并顺序.每一次将Splay大小较小的结点的权值全提取出来塞到较大的里面. 由于权值可能重复出现,所以每个结点记个cnt. 答案统计的时候,就将那个刚塞进去的旋到根,然后答案加上左子树的权值和,再加上(右子树的权值的个数+该结点

【BZOJ2733】永无乡[splay启发式合并or线段树合并]

题目大意:给你一些点,修改是在在两个点之间连一条无向边,查询时求某个点能走到的点中重要度第k大的点.题目中给定的是每个节点的排名,所以实际上是求第k小:题目求的是编号,不是重要度的排名.我一开始差点被这坑了. 网址:http://www.lydsy.com/JudgeOnline/problem.php?id=2733 这道题似乎挺经典的(至少我看许多神犇很早就做了这道题).这道题有两种写法:并查集+(splay启发式合并or线段树合并).我写的是线段树合并,因为--splay不会打+懒得学.

学习笔记::启发式合并

昨天做Tree Rotation,没发现自己写的是暴力,还要了数据...... 然后发现好像必须得用启发式合并 不想学线段树,学了个splay的 --------------------------------------------------- 假设现在有n个点,每个点是一个splay,互不连起来 假设我们每次让两个不连通的splay联通, 所谓启发式:就是把小的合并到大的上,这样使复杂度有保证 怎么合并呢?就是先把小的splay的每个点找出来,然后插入到大的splay ----------

BZOJ 2733: [HNOI2012]永无乡(treap + 启发式合并 + 并查集)

不难...treap + 启发式合并 + 并查集 搞搞就行了 ---------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define rep(i, n) for(int i = 0; i &l

[BZOJ 1483][HNOI 2009]梦幻补丁(有序表启发式合并)

题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1483 分析: 先将不同的颜色的出现位置从小到大用几条链表串起来,然后统计一下答案 对于每次修改,修改一下答案即可,修改之后需要将两个颜色的链表合并就行了,但感觉似乎会TLE? 以下摘录与Hzwer的blog: 1:将两个队列合并,有若干队列,总长度为n,直接合并,最坏O(N), 2:启发式合并呢? 每次我们把短的合并到长的上面去,O(短的长度) 咋看之下没有多大区别, 下面让我们看看

51nod 1907(多项式乘法启发式合并)

题目: 分析: 对于一个确定的生成子图,很明显是在一个连通块上走,走完了再跳到另一个连通块上,假设连通块个数为cnt,那么答案一定是$min(a_{cnt-1},a_cnt,..,a_{n-1})$  那现在的问题就是如何求出对于原图而言,连通块个数分别为1,2..n的生成子图的个数 我们去考虑每条边的贡献 在一个仙人掌上只有树边和回路上的边,对于树边如果删除那么肯定连通块个数+1,对于回路上的边,删除一条边不影响,再后面每删除一条边连通块个数+1 我们可以写出它们的生成函数,然后乘起来 对于树

BZOJ2733 永无乡【splay启发式合并】

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/转载请注明出处,侵权必究,保留最终解释权! Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛.如果从岛 a 出发经过若干座(含

【BZOJ-4668】冷战 并查集 + 启发式合并 + 乱搞

4668: 冷战 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 37  Solved: 24[Submit][Status][Discuss] Description 1946 年 3 月 5 日,英国前首相温斯顿·丘吉尔在美国富尔顿发表“铁幕演说”,正式拉开了冷战序幕. 美国和苏联同为世界上的“超级大国”,为了争夺世界霸权,两国及其盟国展开了数十年的斗争.在这段时期,虽然分歧和冲突严重,但双方都尽力避免世界范围的大规模战争(第三次世界大战)爆发