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. sed 常用命令

    删除以ifeq开头的行 sed -i "/^ifeq/d" file 删除空行 sed -i '/^$/d' file

  2. 【网络协议】IP协议、ARP协议、RARP协议

    IP数据报 IP是TCP/IP协议族中最核心的协议,全部的TCP.UDP.ICMP.IGMP数据都以IP数据报的格式传输.IP仅提供尽力而为的传输服务.假设发生某种错误.IP会丢失该数据.然后发送IC ...

  3. android keyEvent

    http://developer.android.com/reference/android/view/KeyEvent.html

  4. EF(Entity Framework)通用DBHelper通用类,增删改查以及列表

    其中 通用类名:DBhelper 实体类:UserInfo 1 //新增 2 DBHelper<UserInfo> dbhelper = new DBHelper<UserInfo& ...

  5. word2vec学习 spark版

    参考资料: http://ir.dlut.edu.cn/NewsShow.aspx?ID=291 http://www.douban.com/note/298095260/ http://machin ...

  6. scala进阶笔记:函数组合器(combinator)

    collection基础参见之前的博文scala快速学习(二). 本文主要是组合器(combinator),因为在实际中发现很有用.主要参考:http://www.importnew.com/3673 ...

  7. sanic官方文档解析之ssl,debug mode模式和test(测试)

    1,ssl 示例: 可选择的SSLContent from sanic import Sanic import ssl context = ssl.create_default_context(pur ...

  8. socketIO原理图

  9. 如何去除Office Excel的密码保护?

    企图更改Excel文件内容,然而却弹出如下提示: 根据提示,我尝试解除保护表,却要求输入密码: 这就尴尬了=_=密码不是我设定的 问了度娘,找到了解决方案 将Excel文件扩展名更改为rar, 使用压 ...

  10. A喝酒(北京林业大学校赛)

    http://www.jisuanke.com/contest/1410 王大钉喜欢喝酒,存货都喝完了,他就去楼下买,正好楼下的商店为了响应学校的 ACM 校赛推出了优惠活动:凡是在本店买的啤酒,喝完 ...