Counting The Important Pairs CodeChef - TAPAIR
https://vjudge.net/problem/CodeChef-TAPAIR
合法的删除方法:
第一种:桥边与其余任意边
(1)桥*(桥-1)/2(两条桥边)
(2)桥*(m-桥)(桥边+其他边)
第二种:两条非桥边;一定在同一个边双内
对每一个边双求dfs树
(1)两条树边
(定义覆盖:反向边(a,b)覆盖了dfs树上a到b路径中每一条边)
显然,任意边覆盖的路径中都是深度递减/递增的一些点
如果两条树边被完全相同的边集覆盖,那么显然(感性理解)它们处在相同的环的中,因此同时去掉能让这些环断开两个口子,这会产生不连通
如果两条树边被不完全相同的边集覆盖,那么它们处在的环有一些不同,(画图+感性理解)同时去掉不能让环断开
(2)一条树边+一条反向边
当且仅当该树边只被这条反向边覆盖,同时去掉能让环断开
可以对每一条树边j记一个值xo[j],随机给每一条非树边i一个特定的longlong型数p[i],对这条边覆盖的所有边j,使得xo[j]^=p[i]。那么,对于两条树边i,j,xo[i]==xo[j]表明覆盖它们的边集完全相同,xo[i]!=xo[j]表明这个边集不完全相同。具体实现可以用树上差分
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
using namespace std;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define N 500100
#define M 500100
struct E{int to,nxt;}e[M<<];
int f1[N],ne=;
int dfn[N],dfc;
bool bri[M];
void me(int a,int b)
{
e[++ne].to=b;e[ne].nxt=f1[a];f1[a]=ne;
e[++ne].to=a;e[ne].nxt=f1[b];f1[b]=ne;
}
int dfs(int u,int last)
{
int lowu=dfn[u]=++dfc,v,lowv;
for(int k=f1[u];k;k=e[k].nxt)
{
v=e[k].to;
if(!dfn[v])
{
lowv=dfs(v,k);
lowu=min(lowu,lowv);
if(lowv==dfn[v]) bri[k/]=;
}
else if(dfn[v]<dfn[u]&&k!=(last^))
lowu=min(lowu,dfn[v]);
}
return lowu;
}
ll randd()
{
return (ll(rand())<<)|rand();
}
ll fui(){return ;}
int now=;
int eccno[N],cnt;
int n,m,brii;ll ans;
int dep[N];
bool vis[N],tree[M];
int ga[M],gb[M];
map<ll,int> s;
pair<ll,bool> xo[N];//xo[i]表示i到父亲间的边被覆盖的情况
void dfs1(int u)
{
vis[u]=;
for(int k=f1[u];k;k=e[k].nxt)
if(!bri[k/]&&!vis[e[k].to])
{
tree[k/]=;
dep[e[k].to]=dep[u]+;
dfs1(e[k].to);
}
}
void dfs2(int u)
{
vis[u]=;
for(int k=f1[u];k;k=e[k].nxt)
if(!bri[k/]&&!vis[e[k].to])
{
dfs2(e[k].to);
xo[u].fi^=xo[e[k].to].fi;
}
}
int main()
{
int i,j,a,b;ll p;
scanf("%d%d",&n,&m);
for(i=;i<=m;i++)
{
scanf("%d%d",&a,&b);ga[i]=a;gb[i]=b;
me(a,b);
}
for(i=;i<=n;i++) if(!dfn[i]) dfs(i,-);
for(i=;i<=m;i++) brii+=bri[i];
ans+=ll(brii)*(brii-)/;
ans+=ll(brii)*(m-brii);
for(i=;i<=n;i++) if(!vis[i]) dfs1(i);
for(i=;i<=m;i++)
if(!bri[i]&&!tree[i])
{
a=ga[i];b=gb[i];
if(dep[a]<dep[b]) swap(a,b);
p=randd();s[p]++;
xo[b].fi^=p;xo[a].fi^=p;
}
memset(vis,,sizeof(vis));
for(i=;i<=n;i++) if(!vis[i]) xo[i].se=,dfs2(i);
sort(xo+,xo+n+);
for(i=,j=;i<=n;i++)
{
if(!xo[i].se) j++;
if(i==n||xo[i]!=xo[i+])
{
ans+=ll(j)*(j-)/;
j=;
}
if(!xo[i].se) ans+=s[xo[i].fi];
}
printf("%lld",ans);
return ;
}
拉一份题解:
codechef Counting The Important Pairs
传送门
给一副很大的图,询问有多少对边,删除它们后图会不连通。
首先,如果有桥,那么桥跟任意的边组合都可以达到目的。
然后对于每个连通块分别考虑(在两个连通块里面分别拆一条,无关痛痒。。
在一个连通块里面,先搞出一个dfs树,可以将边分成树边跟非树边,所有的非树边都是backedge。可以想象,如果去掉两条非树边,没啥用。所以必须得去掉一条树边。
所以可能是这样的两种组合:
1:一条树边+一条backedge
2:两条树边
再仔细观察一下,可以发现,如果两条树边被backedge覆盖的情况是不同的,相当于这两条树边是在两个不同的环里面,删除它们是没用的,所以我们应该删除两条覆盖情况相同的树边。
然后就是当某段路径只被一条backedge覆盖的时候,去掉这条backedge后,随便去掉一条树边就可以使图不连通。
所以我们要做的就是找出所有被覆盖情况相同的路径。
注意,应该要先从度大于2的点开始搜,因为这种点肯定可以当做路径的开头或者简单环的开头,度数大于2的点肯定是两个以上环的交点,搜到这种点,路径就终结了,因为要是把路径放到两个环里,怎么删都不行。
p
如上图所示,如果走到了度数为2的点,可以继续增加路径的长度,如果走到了度数大于2的点,比如1号点走到2号点,1到父亲的边,跟2到1的边的覆盖情况肯定不同了,因为2或者2的子孙节点出发肯定会有一条backedge往1的上面去的。
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N = 100010;
const int M = 300010;
int pnt[M * 2], nxt[M * 2], head[N], E;
int low[N], dfn[N], tdfn, deg[N];
bool vis[N];
void add_edge(int a, int b)
{
pnt[E] = b;
nxt[E] = head[a];
head[a] = E++;
}
int bridge;
void dfs(int u, int fa)
{
low[u] = dfn[u] = ++tdfn;
for(int i = head[u]; ~i; i = nxt[i]) {
int v = pnt[i];
if(!dfn[v]) {
dfs(v, u);
if(low[v] > dfn[u]) {
bridge++;
deg[u]--, deg[v]--;
}
low[u] = std::min(low[u], low[v]);
} else if(v != fa) {
low[u] = std::min(low[u], dfn[v]);
}
}
}
long long len, ret;
void go(int u, int fa)
{
vis[u] = true;
if(deg[u] > 2) {
ret += len * (len - 1) >> 1;
for(int i = head[u]; ~i; i = nxt[i]) {
int v = pnt[i];
if(!vis[v] && low[v] <= dfn[u]) {
len = 1;
go(v, u);
}
}
} else {
for(int i = head[u]; ~i; i = nxt[i]) {
int v = pnt[i];
if(v != fa && low[v] <= dfn[u]) {
if(vis[v]) {
len++;
ret += len * (len - 1) >> 1;
len = 0;
} else {
len++;
go(v, u);
}
}
}
}
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
std::fill(head, head + n + 1, -1);
for(int i = 0, a, b; i < m; i++) {
scanf("%d%d", &a, &b);
deg[a]++; deg[b]++;
add_edge(a, b);
add_edge(b, a);
}
dfs(1, -1);
ret += 1LL * bridge * (bridge - 1) / 2;
ret += 1LL * bridge * (m - bridge);
for(int i = 1; i <= n; i++) {
if(!vis[i] && deg[i] > 2) {
go(i, -1);
}
}
for(int i = 1; i <= n; i++) {
if(!vis[i] && deg[i] == 2) {
go(i, -1);
}
}
printf("%lld\n", ret);
return 0;
}
当然,有一种更优雅的做法,将每条backedge都随机一个值,然后每条树边的值是覆盖它的所有backedge的异或和,现在只需要在异或和相同的边里面随便删除两条就好了。
这种打标记的姿势还真是赞。
/* **********************************************
Created Time: 2014/9/9 13:19:05
File Name : C.cpp
*********************************************** */
#include <iostream>
#include <fstream>
#include <cstring>
#include <climits>
#include <ctime>
#include <deque>
#include <cmath>
#include <queue>
#include <stack>
#include <list>
#include <map>
#include <set>
#include <utility>
#include <sstream>
#include <complex>
#include <string>
#include <vector>
#include <cstdio>
#include <bitset>
#include <functional>
#include <algorithm>
typedef unsigned long long LL;
const int N = 100010;
const int M = 300010;
LL val[N];
int fa[N];
int stack[N];
int head[N];
int pnt[M * 2];
int nxt[M * 2];
int E;
int cover[N];
int start[M * 2];
int dep[N];
LL myrand()
{
LL ret = 0;
for(int i = 0; i < 4; i++) {
ret = ret << 16;
ret ¦= rand();
}
return ret;
}
void add_edge(int a, int b)
{
start[E] = a;
pnt[E] = b;
nxt[E] = head[a];
head[a] = E++;
}
int tot;
void dfs(int u, int f)
{
stack[++tot] = u;
fa[u] = f;
dep[u] = dep[f] + 1;
for(int i = head[u]; i != -1; i = nxt[i]) {
if(!dep[pnt[i]]) {
dfs(pnt[i], u);
}
}
}
int main()
{
srand(time(NULL));
int n, m, a, b;
scanf("%d%d", &n, &m);
std::fill(head + 1, head + n + 1, -1);
for(int i = 0; i < m; i++) {
scanf("%d%d", &a, &b);
add_edge(a, b);
add_edge(b, a);
}
dfs(1, 0);
for(int i = 0; i < 2*m; i += 2) {
a = start[i], b = pnt[i];
if(dep[a] < dep[b]) {
std::swap(a, b);
}
cover[a]++, cover[b]--;
if(dep[b] + 1 == dep[a]) {
continue;
}
LL v = myrand();
val[b] ^= v, val[a] ^= v;
}
for(int i = n; i >= 1; i--) {
cover[fa[stack[i]]] += cover[stack[i]];
val[fa[stack[i]]] ^= val[stack[i]];
}
long long ret = std::count(cover + 1, cover + 1 + n, 2);
long long bridge = std::count(cover + 1, cover + n + 1, 1);
ret += bridge * (bridge - 1) / 2 + bridge * (m - bridge);
std::sort(val + 1, val + n + 1);
for(int i = 1, len; i <= n; i++) {
if(val[i] == 0) {
continue;
}
if(val[i] == val[i - 1]) {
ret += len++;
} else {
len = 1;
}
}
printf("%lld\n", ret);
return 0;
}
还有一道一样的题,一起贴了吧
http://210.33.19.103/contest/895/problem/2
量子通讯
题目描述:
有N个强相互作用力探测器在太空中航行。M对探测器之间可以通过量子纠缠进行双向通讯,这样所有的探测器都可以直接或间接地联系。
由于量子纠缠态在被干扰后就会消失,因此可以通过这种方式破坏某些双向通讯。
受技术手段限制,只能破坏两个这样的量子纠缠。有多少种破坏方法可以把所有探测器分成至少两个互相无法联系的部分?
输入格式:
输入文件的第一行是两个正整数N,M,代表探测器的数量和量子纠缠的数量。
接下来的M行每行有两个正整数,代表一对能互相直接通讯的探测器。由于量子通讯的原理是将一个自旋为零的粒子分裂成两个自旋相反的粒子,因此两个探测器之间可能会建立多个量子通讯。同时,某个探测器也可能和其自身建立量子通讯。输入保证所有的探测器都能直接或间接联系。
输出格式:
输出一行一个整数,即方案数。
输入样例:
3 3
1 2
2 3
3 1
输出样例:
3
提示:
破坏任意两个量子纠缠都会把3个探测器分成互相无法联系的两部分,因此共有C(3,2)=3种破坏方法。
对于30%的数据,1<=N<=20,1<=M<=40
对于50%的数据,1<=N<=500,1<=M<=1000
对于100%的数据,1<=N<=2000,1<=M<=100000.
Counting The Important Pairs CodeChef - TAPAIR的更多相关文章
- Codechef TAPAIR Counting the important pairs 随机化、树上差分
传送门 题意:给出一个$N$个点.$M$条边的无向连通图,求有多少组无序数对$(i,j)$满足:割掉第$i$条边与第$j$条边之后,图变为不连通.$N \leq 10^5 , M \leq 3 \ti ...
- BlackJack Strategy
GAME SPEC: 2-deck, 104 cards total. Bellagio has 2-deck and 6-deck games. based on hard 17, dealer h ...
- One-Way Streets (oneway)
One-Way Streets (oneway) 题目描述 Once upon a time there was a country with nn cities and mm bidirection ...
- CodeChef Counting on a directed graph
Counting on a directed graph Problem Code: GRAPHCNT All submissions for this problem are available. ...
- 51nod 1290 Counting Diff Pairs | 莫队 树状数组
51nod 1290 Counting Diff Pairs | 莫队 树状数组 题面 一个长度为N的正整数数组A,给出一个数K以及Q个查询,每个查询包含2个数l和r,对于每个查询输出从A[i]到A[ ...
- 51nod 1290 Counting Diff Pairs 莫队 + bit
一个长度为N的正整数数组A,给出一个数K以及Q个查询,每个查询包含2个数l和r,对于每个查询输出从A[i]到A[j]中,有多少对数,abs(A[i] - A[j]) <= K(abs表示绝对值) ...
- Codechef CNTL Counting is life 生成函数
传送门--Vjudge 第一问很氵,如果\(K,N\)同奇偶就是\(2^K-1\),否则就是\(2^K-2\) 第二问似乎是可重排列,考虑指数型生成函数. 如何限制某些数必须要出现奇数/偶数次?考虑\ ...
- Codechef Sad Pairs——圆方树+虚树+树上差分
SADPAIRS 删点不连通,点双,圆方树 非割点:没有影响 割点:子树DP一下 有不同颜色,所以建立虚树 在圆方树上dfs时候 如果当前点是割点 1.统计当前颜色虚树上的不连通点对,树形DP即可 2 ...
- 题解【51nod 1290 Counting Diff Pairs】
Description 求区间内有多少对 \((i,j)\) 满足 \(|a_i - a_j| \leq k\) Solution 可以莫队做(万能的莫队) 只需要考虑加入一个数会产生多少贡献即可 离 ...
随机推荐
- 初识glib(1)
最近搞DLNA,发现download的源码有许多glib库的使用.于是在Ubuntu中安装了glib库,以及简单测试了一些glib库函数,以此增加对glib的了解. 概述:glib库是Linux平台下 ...
- VUE 之 路由 VueRouter
1.VueRouter的安装 1.1.https://unpkg.com/vue-router/dist/vue-router.js下载安装. 1.2.<script src="./s ...
- HDU 6108 小C的倍数问题 【数学】 (2017"百度之星"程序设计大赛 - 初赛(A))
小C的倍数问题 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Sub ...
- 更改scroll样式
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ ::-webkit-scrollbar { width: 2px; height: 80%; background: #fff; } /*定 ...
- 对于iOS 7 隐藏特性和解决之道
当 iOS7 刚发布的时候,全世界的苹果开发人员都立马尝试着去编译他们的app,接着再花上数月的时间来修复任何出现的故障,甚至重做app.这样的结果,使得人们根本无暇去探究 iOS7 所带来的新东西. ...
- java多线程实现简单队列
1.创建Queue.java public class Queue { private LinkedList<Object> list = new LinkedList<Object ...
- Silverlight中使用MVVM(2)
Silverlight中使用MVVM(1)--基础 Silverlight中使用MVVM(2)—提高 Silverlight中使用MVVM(3)—进阶 Silverlight中使用MVVM(4)—演练 ...
- Linux 下WAS的java版本查看
1.查找linux的详细版本号: A.cat /proc/version B.lsb_release -a(可以查出是否为redhat开发的) C.uname -a 2.Linux的java版本 A. ...
- Python小练习_将数据库中表数据存到redis里
# ##练习:将xxx数据库中my_user表中数据存到redis里面# 分析: pymysql.json.redis# 1.连接数据库,查到数据库里面所有的数据,游标类型要用pymysql.curs ...
- iconMoon---小图标小记
IcoMoon 是一个免费的图标库.可以下载自己需要的图标 三.使用流程.操作演示 进入主页,点击下图所示区域开始: 每个图标你都是可以自己进行标记的(移上去会看到Edit, 点击之),然后—— 注: ...