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的更多相关文章

  1. Codechef TAPAIR Counting the important pairs 随机化、树上差分

    传送门 题意:给出一个$N$个点.$M$条边的无向连通图,求有多少组无序数对$(i,j)$满足:割掉第$i$条边与第$j$条边之后,图变为不连通.$N \leq 10^5 , M \leq 3 \ti ...

  2. BlackJack Strategy

    GAME SPEC: 2-deck, 104 cards total. Bellagio has 2-deck and 6-deck games. based on hard 17, dealer h ...

  3. One-Way Streets (oneway)

    One-Way Streets (oneway) 题目描述 Once upon a time there was a country with nn cities and mm bidirection ...

  4. CodeChef Counting on a directed graph

    Counting on a directed graph Problem Code: GRAPHCNT All submissions for this problem are available. ...

  5. 51nod 1290 Counting Diff Pairs | 莫队 树状数组

    51nod 1290 Counting Diff Pairs | 莫队 树状数组 题面 一个长度为N的正整数数组A,给出一个数K以及Q个查询,每个查询包含2个数l和r,对于每个查询输出从A[i]到A[ ...

  6. 51nod 1290 Counting Diff Pairs 莫队 + bit

    一个长度为N的正整数数组A,给出一个数K以及Q个查询,每个查询包含2个数l和r,对于每个查询输出从A[i]到A[j]中,有多少对数,abs(A[i] - A[j]) <= K(abs表示绝对值) ...

  7. Codechef CNTL Counting is life 生成函数

    传送门--Vjudge 第一问很氵,如果\(K,N\)同奇偶就是\(2^K-1\),否则就是\(2^K-2\) 第二问似乎是可重排列,考虑指数型生成函数. 如何限制某些数必须要出现奇数/偶数次?考虑\ ...

  8. Codechef Sad Pairs——圆方树+虚树+树上差分

    SADPAIRS 删点不连通,点双,圆方树 非割点:没有影响 割点:子树DP一下 有不同颜色,所以建立虚树 在圆方树上dfs时候 如果当前点是割点 1.统计当前颜色虚树上的不连通点对,树形DP即可 2 ...

  9. 题解【51nod 1290 Counting Diff Pairs】

    Description 求区间内有多少对 \((i,j)\) 满足 \(|a_i - a_j| \leq k\) Solution 可以莫队做(万能的莫队) 只需要考虑加入一个数会产生多少贡献即可 离 ...

随机推荐

  1. 使用外部 toolchain 编译 openwrt

    默认编译 openwrt 时会先编译一套 toolchain. 这个步骤耗时较长. 使用外部 toolchain 可以多个 project 共用一套 toolchain , 而且也不重再编译它了. 省 ...

  2. linux输入子系统(6)-input子系统介绍及结构图

    注:本系列转自: http://www.ourunix.org/post/290.html input子系统介绍         输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备,其一 ...

  3. linux进程间通信消息队列:msgsnd: Invalid argument

    今天写了个消息队列的小测试程序结果send端程序总是出现:msgsnd: Invalid argument,搞了半个小时也没搞明白,后来查资料发现我将(st_msg_buf.msg_type = 0; ...

  4. mysql下distinct和group by区别对比

    在数据表中记录了用户验证时使用的书目,现在想取出所有书目,用DISTINCT和group by都取到了我想要的结果,但我发现返回结果排列不同,distinct会按数据存放顺序一条条显示,而group ...

  5. Appium启动报Permission Denial的问题

    前言 在Android真机上跑自动化脚本时,发现在启动App时报java.lang.SecurityException: Permission Denial: starting Intent : 原先 ...

  6. 基于mac系统的apacheserver的使用流程

    打开终端.输入下面命令:sudo apachectl start 此时Apache已经开启.在浏览器中输入本地ip地址能够看到it works! 打开前往----电脑------Macintosh H ...

  7. POJ 2750 Potted Flower(线段树+dp)

    题目链接 虽然是看的别的人思路,但是做出来还是挺高兴的. 首先求环上最大字段和,而且不能是含有全部元素.本来我的想法是n个元素变为2*n个元素那样做的,这样并不好弄.实际可以求出最小值,总和-最小,就 ...

  8. 网页 H5“线条” 特效实现方式(canvas-nest)

    先上图 (看博客空白处也可以呦): 前一阵浏览网站的时候,发现了这个好玩的东西,一直想找找怎么实现的,今天忙里偷闲,上网搜了一下,发现实现起来特别简单. 只需要在网页body里引入一个<scri ...

  9. 实用API大全

    有道翻译API http://fanyi.youdao.com/openapi 有道翻译API支持中英互译,同时获得有道翻译结果和有道词典结果(可能没有),返回格式为XML或JSON.   百度翻译A ...

  10. 合肥 专业做APP(安卓,ios) 微信公共平台

    合肥 专业做APP(安卓,ios) 微信公共平台 电话:15715696592