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

题目描述

要致富,先撸树。

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

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

输入格式

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

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

输出格式

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

数据规模与约定

对于 $30\%$ 的数据,$1\le N\le 20,1\le M\le40$

对于$50\%$的数据,$1\le N\le500,1\le M\le1000$

对于$100\%$的数据,$1\le N\le50000,1\le M\le100000$

保证刚开始图是联通的。

时间限制:1s

空间限制:512M


题目分析

题外话

重边

好好分析一下

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

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

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

接下去考虑算法实现。

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

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

 #include<bits/stdc++.h>
typedef unsigned long long ll;
typedef std::pair<ll, ll> pr;
const int maxn = ;
const int maxm = ; struct Edge
{
int v,id;
Edge(int a=, int b=):v(a),id(b) {}
}edges[maxm];
int n,m,u[maxm],v[maxm],fa[maxn];
int edgeTot,head[maxn],nxt[maxm];
pr eval[maxm],ev[maxn];
ll ans; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch=getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch=getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
pr operator ^(pr a, pr b){return pr(a.first^b.first, a.second^b.second);}
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
void addedge(int u, int v, int id)
{
edges[++edgeTot] = Edge(v, id), nxt[edgeTot] = head[u], head[u] = edgeTot;
edges[++edgeTot] = Edge(u, id), nxt[edgeTot] = head[v], head[v] = edgeTot;
}
void count(int x, int fa)
{
for (int i=head[x]; i!=-; i=nxt[i])
{
int v = edges[i].v, id = edges[i].id;
if (v!=fa)
count(v, x), ev[x] = ev[x]^ev[v], eval[id] = ev[v];
}
}
int main()
{
freopen("lutree.in","r",stdin);
freopen("lutree.out","w",stdout);
memset(head, -, sizeof head);
srand(), n = read(), m = read();
for (int i=; i<=n; i++) fa[i] = i;
for (int i=,uf,vf; i<=m; i++)
{
uf = get(u[i] = read()), vf = get(v[i] = read());
if (uf^vf){
addedge(u[i], v[i], i), fa[uf] = vf;
}else{
eval[i] = pr(1ll*rand()*rand()*rand()*rand()*rand(), 1ll*rand()*rand()*rand()*rand()*rand());
}
}
for (int i=; i<=m; i++)
if (eval[i].first||eval[i].second){
ev[u[i]] = ev[u[i]]^eval[i], ev[v[i]] = ev[v[i]]^eval[i];
}
count(, );
std::sort(eval+, eval+m+);
for (int i=, j=; i<=m; i=j)
{
if ((eval[i].first==)&&(eval[i].second==))
ans += m-i, j = i+;
else{
for (j=i; j<=m&&eval[i]==eval[j]; j++);
ans += 1ll*(j-i-)*(j-i)/2ll;
}
}
printf("%lld\n",ans);
return ;
}

END

【思维题 集合hash 树上差分】11.5撸树的更多相关文章

  1. [BZOJ3307]:雨天的尾巴(LCA+树上差分+权值线段树)

    题目传送门 题目描述: N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入格式: 第一 ...

  2. 【线性基 集合hash】uoj#138. 【UER #3】开学前的涂鸦

    还需要加强分析题目特殊性质,设计对应特殊算法,少想多写大力dfs剪枝不要管MLETLE直接上的能力 红包是一个有艺术细胞的男孩子. 红包由于NOI惨挂心情不好,暑假作业又多,于是他开始在作业本上涂鸦. ...

  3. 差分数组 and 树上差分

    差分数组 定义 百度百科中的差分定义 //其实这完全和要讲的没关系 qwq 进去看了之后是不是觉得看不懂? 那我简单概括一下qwq 差分数组de定义:记录当前位置的数与上一位置的数的差值. 栗子 容易 ...

  4. 刷题总结——小c找朋友(bzoj4264 集合hash)

    题目: Description 幼儿园里有N个小C,两个小C之间可能是朋友也可能不是.所有小C之间的朋友关系构成了一个无向图,这个无向图中有M条边. 园长ATM发现对于两个(不同的)小Ci和j,如果其 ...

  5. BZOJ 2734 洛谷 3226 [HNOI2012]集合选数【状压DP】【思维题】

    [题解] 思维题,看了别人的博客才会写. 写出这样的矩阵: 1,3,9,... 2,6,18,... 4,12.36,... 8,24,72,... 我们要做的就是从矩阵中选出一些数字,但是不能选相邻 ...

  6. bzoj3307 雨天的尾巴题解及改题过程(线段树合并+lca+树上差分)

    题目描述 N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入格式 第一行数字N,M接下 ...

  7. 洛谷 P4749 - [CERC2017]Kitchen Knobs(差分转换+dp,思维题)

    题面传送门 一道挺有意思的思维题. 首先有一个 obvious 的结论,就是对于每个炉子,要么转到哪里都符合条件,要么存在唯一的最大值.对于转到哪儿都符合条件的炉子我们 duck 不必考虑它,故我们只 ...

  8. P1600 天天爱跑步[桶+LCA+树上差分]

    题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵 ...

  9. BZOJ2588 主席树 + 树上差分

    https://www.lydsy.com/JudgeOnline/problem.php?id=2588 题意:强制在线的询问树链权值第K小(无修) 这种类似于第K小的题,一般容易想到主席树,但是树 ...

随机推荐

  1. js函数-构成

    前言 函数是一种封装,在任何语言中都是一个核心概念.在js中,函数是做为对象的子类型存在的.可以拥有自己的属性和方法,可以做为值进行传递,这两个特性让js拥有使用函数式编程的能力. 函数的声明 字面量 ...

  2. Codeforces Round #432 (Div. 2, based on IndiaHacks Final Round 2017) C

    You are given set of n points in 5-dimensional space. The points are labeled from 1 to n. No two poi ...

  3. Codeforces Round #378 (Div. 2) D. Kostya the Sculptor 分组 + 贪心

    http://codeforces.com/contest/733/problem/D 给定n个长方体,然后每个长方体都能选择任何一个面,去和其他长方体接在一起,也可以自己一个,要求使得新的长方体的最 ...

  4. P4869 罪犯分组

    思路: 明显的dp,虽然我想到了二进制模拟,想到了转移,但还是先看了题解,原来真是这样,,,,不是第三题吗? 用f[i]表示,对于前i个罪犯最少需要分几组. 对于每个状态用二进制表示,第i位上1,0表 ...

  5. CA、公钥、私钥 概要

    CA.公钥.私钥 概要 现在在开发中遇到一个需求,需要使用tls加密技术,之前并没有了解过,这里来做一个关于CA,公钥,密钥的总结,至于怎么生成这儿就不讲了,如果有机会可以再开一个单章来讲一下. 现在 ...

  6. AngularJS中最重要的核心功能

    以下是AngularJS中最重要的核心功能: 数据绑定: 模型和视图组件之间的数据自动同步. 适用范围: 这些对象参考模型.它们充当控制器和视图之间的胶水. 控制器: 这些Javascript函数绑定 ...

  7. 在使用seek()函数时,有时候会报错为 “io.UnsupportedOperation: can't do nonzero cur-relative seeks”,代码如下:

    __author__ = 'ZHHT' #!/usr/bin/env python # -*- coding:utf-8 -*- import os f = open("test1" ...

  8. C# 初始学习心情

    当听说需要转做.net的时候.内心是忐忑不安的.因为突然从前端转向后端,几乎完全颠倒了...一个注重界面实现功能.一个注重逻辑的开发,然并卵,服从需求吧. 虽说公司需要你转.但是时间是不允许的,所以只 ...

  9. JFinal视频教程

    最近开始录制JFinal视频教程,发布在腾讯课堂上,免费公开面向JFinal开发者,作为JFinal开发者入门学习.实际项目用遇到问题寻找解决方案的最好途径. 目前JFinal课程已经开始更新. 腾讯 ...

  10. 一键部署基于GitLab的自托管Git项目仓库

    https://market.azure.cn/Vhd/Show?vhdId=9851&version=11921 产品详情 产品介绍GitLab https://about.gitlab.c ...