zoukankan      html  css  js  c++  java
  • 【思维题 集合hash 树上差分】11.5撸树

     要注重问题的转化和一些结论的推断

    题目描述

    要致富,先撸树。

    一棵树的形状可以简化为一张 $N$ 个点 $M$ 条边的图,由于装备条件限制,你只有撸两次,也就是删去两条边,当这张图不联通时,就意味着树倒了。

    现在你想知道有多少种方案能撸倒这棵树。

    输入格式

    第一行两个正整数 $n,m$

    接下来 $m$ 行,每行两个正整数,表示一条边。

    输出格式

    输出一个数,表示方案数。

    数据规模与约定

    对于 $30\%$ 的数据,$1le Nle 20,1le Mle40$

    对于$50\%$的数据,$1le Nle500,1le Mle1000$

    对于$100\%$的数据,$1le Nle50000,1le Mle100000$

    保证刚开始图是联通的。

    时间限制:1s

    空间限制:512M


    题目分析

    题外话

    重边

    好好分析一下

    图的问题先转化为树,于是先预处理出树边与非树边,再来考虑非树边对于树的影响。

    首先是一些定义:对每一条,如果是树边就使用哈希记录下经过它的非树边,并将这个哈希值称作“经过哈希值”、边的数量称作“经过数”;如果是非树边,“经过哈希值”就是它的哈希值本身、“经过数”不计。

    然后是结论:若一条边(树边)的经过数为0(也就意味着它是一条割边),说明选了它再任选一条边都可行;若两条的经过哈希值相同,意味着必须同时取两条这种边才能将图分为两半。

    接下去考虑算法实现。

    注意到这里哈希是集合哈希,那么一种经典方法就是rand一个大数(long long),再异或起来。对于一条非树边$(u,v)$,我们需要把树上路径$u->v$这一段都加上标记,因此考虑树上差分。这里有一个小技巧,因为此处操作是异或,而两端点同时异或一个相同的数,那么就不用像常规那样求出lca并除去贡献,只需要在打完标记之后再从上至下dfs一遍记录每一条边的哈希值。

    还有一个处理的技巧:将哈希开成unsigned long long;双哈希开std::pair。如此一来排序时候就自然而然将经过数为0的边排在前面,天然保证了答案的顺序。

     1 #include<bits/stdc++.h>
     2 typedef unsigned long long ll;
     3 typedef std::pair<ll, ll> pr;
     4 const int maxn = 50035;
     5 const int maxm = 200035;
     6 
     7 struct Edge
     8 {
     9     int v,id;
    10     Edge(int a=0, int b=0):v(a),id(b) {}
    11 }edges[maxm];
    12 int n,m,u[maxm],v[maxm],fa[maxn];
    13 int edgeTot,head[maxn],nxt[maxm];
    14 pr eval[maxm],ev[maxn];
    15 ll ans;
    16 
    17 int read()
    18 {
    19     char ch = getchar();
    20     int num = 0;
    21     bool fl = 0;
    22     for (; !isdigit(ch); ch=getchar())
    23         if (ch=='-') fl = 1;
    24     for (; isdigit(ch); ch=getchar())
    25         num = (num<<1)+(num<<3)+ch-48;
    26     if (fl) num = -num;
    27     return num;
    28 }
    29 pr operator ^(pr a, pr b){return pr(a.first^b.first, a.second^b.second);}
    30 int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
    31 void addedge(int u, int v, int id)
    32 {
    33     edges[++edgeTot] = Edge(v, id), nxt[edgeTot] = head[u], head[u] = edgeTot;
    34     edges[++edgeTot] = Edge(u, id), nxt[edgeTot] = head[v], head[v] = edgeTot;
    35 }
    36 void count(int x, int fa)
    37 {
    38     for (int i=head[x]; i!=-1; i=nxt[i])
    39     {
    40         int v = edges[i].v, id = edges[i].id;
    41         if (v!=fa)
    42             count(v, x), ev[x] = ev[x]^ev[v], eval[id] = ev[v];
    43     }
    44 }
    45 int main()
    46 {
    47     freopen("lutree.in","r",stdin);
    48     freopen("lutree.out","w",stdout);
    49     memset(head, -1, sizeof head);
    50     srand(3627), n = read(), m = read();
    51     for (int i=1; i<=n; i++) fa[i] = i;
    52     for (int i=1,uf,vf; i<=m; i++)
    53     {
    54         uf = get(u[i] = read()), vf = get(v[i] = read());
    55         if (uf^vf){
    56             addedge(u[i], v[i], i), fa[uf] = vf;
    57         }else{
    58             eval[i] = pr(1ll*rand()*rand()*rand()*rand()*rand(), 1ll*rand()*rand()*rand()*rand()*rand());
    59         }
    60     }
    61     for (int i=1; i<=m; i++)
    62         if (eval[i].first||eval[i].second){
    63             ev[u[i]] = ev[u[i]]^eval[i], ev[v[i]] = ev[v[i]]^eval[i];
    64         }
    65     count(1, 0);
    66     std::sort(eval+1, eval+m+1);
    67     for (int i=1, j=0; i<=m; i=j)
    68     {
    69         if ((eval[i].first==0)&&(eval[i].second==0))
    70             ans += m-i, j = i+1;
    71         else{
    72             for (j=i; j<=m&&eval[i]==eval[j]; j++);
    73             ans += 1ll*(j-i-1)*(j-i)/2ll;
    74         }
    75     }
    76     printf("%lld
    ",ans);
    77     return 0;
    78 }

    END

  • 相关阅读:
    Delphi从Internet下载文件
    datasnap 上传/下载大文件(本Demo以图传片文件为例)
    delphi 理解ParamStr
    delphi2010多线程编程教程
    QQ2008自动聊天精灵delphi源码
    Delphi使用Indy、ICS组件读取网页
    UniDac 使用日记(转)
    delphi xe5 安卓 配置sqlite
    Netty内存管理器ByteBufAllocator及内存分配
    初识内存分配ByteBuf
  • 原文地址:https://www.cnblogs.com/antiquality/p/9911369.html
Copyright © 2011-2022 走看看