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]表明这个边集不完全相同。具体实现可以用树上差分

  1 #include<cstdio>
  2 #include<algorithm>
  3 #include<cstring>
  4 #include<vector>
  5 #include<map>
  6 using namespace std;
  7 #define fi first
  8 #define se second
  9 #define mp make_pair
 10 #define pb push_back
 11 typedef long long ll;
 12 typedef unsigned long long ull;
 13 typedef pair<int,int> pii;
 14 #define N 500100
 15 #define M 500100
 16 struct E{int to,nxt;}e[M<<1];
 17 int f1[N],ne=1;
 18 int dfn[N],dfc;
 19 bool bri[M];
 20 void me(int a,int b)
 21 {
 22     e[++ne].to=b;e[ne].nxt=f1[a];f1[a]=ne;
 23     e[++ne].to=a;e[ne].nxt=f1[b];f1[b]=ne;
 24 }
 25 int dfs(int u,int last)
 26 {
 27     int lowu=dfn[u]=++dfc,v,lowv;
 28     for(int k=f1[u];k;k=e[k].nxt)
 29     {
 30         v=e[k].to;
 31         if(!dfn[v])
 32         {
 33             lowv=dfs(v,k);
 34             lowu=min(lowu,lowv);
 35             if(lowv==dfn[v])    bri[k/2]=1;
 36         }
 37         else if(dfn[v]<dfn[u]&&k!=(last^1))
 38             lowu=min(lowu,dfn[v]);
 39     }
 40     return lowu;
 41 }
 42 ll randd()
 43 {
 44     return (ll(rand())<<32)|rand();
 45 }
 46 ll fui(){return 2;}
 47 int now=23;
 48 int eccno[N],cnt;
 49 int n,m,brii;ll ans;
 50 int dep[N];
 51 bool vis[N],tree[M];
 52 int ga[M],gb[M];
 53 map<ll,int> s;
 54 pair<ll,bool> xo[N];//xo[i]表示i到父亲间的边被覆盖的情况
 55 void dfs1(int u)
 56 {
 57     vis[u]=1;
 58     for(int k=f1[u];k;k=e[k].nxt)
 59         if(!bri[k/2]&&!vis[e[k].to])
 60         {
 61             tree[k/2]=1;
 62             dep[e[k].to]=dep[u]+1;
 63             dfs1(e[k].to);
 64         }
 65 }
 66 void dfs2(int u)
 67 {
 68     vis[u]=1;
 69     for(int k=f1[u];k;k=e[k].nxt)
 70         if(!bri[k/2]&&!vis[e[k].to])
 71         {
 72             dfs2(e[k].to);
 73             xo[u].fi^=xo[e[k].to].fi;
 74         }
 75 }
 76 int main()
 77 {
 78     int i,j,a,b;ll p;
 79     scanf("%d%d",&n,&m);
 80     for(i=1;i<=m;i++)
 81     {
 82         scanf("%d%d",&a,&b);ga[i]=a;gb[i]=b;
 83         me(a,b);
 84     }
 85     for(i=1;i<=n;i++)    if(!dfn[i])    dfs(i,-1);
 86     for(i=1;i<=m;i++)    brii+=bri[i];
 87     ans+=ll(brii)*(brii-1)/2;
 88     ans+=ll(brii)*(m-brii);
 89     for(i=1;i<=n;i++)    if(!vis[i])    dfs1(i);
 90     for(i=1;i<=m;i++)
 91         if(!bri[i]&&!tree[i])
 92         {
 93             a=ga[i];b=gb[i];
 94             if(dep[a]<dep[b])    swap(a,b);
 95             p=randd();s[p]++;
 96             xo[b].fi^=p;xo[a].fi^=p;
 97         }
 98     memset(vis,0,sizeof(vis));
 99     for(i=1;i<=n;i++)    if(!vis[i])    xo[i].se=1,dfs2(i);
100     sort(xo+1,xo+n+1);
101     for(i=1,j=0;i<=n;i++)
102     {
103         if(!xo[i].se)    j++;
104         if(i==n||xo[i]!=xo[i+1])
105         {
106             ans+=ll(j)*(j-1)/2;
107             j=0;
108         }
109         if(!xo[i].se)    ans+=s[xo[i].fi];
110     }
111     printf("%lld",ans);
112     return 0;
113 }


拉一份题解:

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.

原文地址:https://www.cnblogs.com/hehe54321/p/9295996.html

时间: 2024-10-08 21:35:05

Counting The Important Pairs CodeChef - TAPAIR的相关文章

Codechef Counting the important pairs 树上差分

传送门 题意:给出一个$N$个点.$M$条边的无向连通图,求有多少组无序数对$(i,j)$满足:割掉第$i$条边与第$j$条边之后,图变为不连通.$N \leq 10^5 , M \leq 3 \times 10^5$ 竟然随机化,歪果仁的思想好灵活qwq肯定是数据结构做多了 看起来很像割边,考虑$tarjan$,但是边散连通分量并不是很好实现,而且有很多特殊情况需要判断,所以我们考虑另外的算法 考虑$tarjan$时建出的一棵树.对于它来说,在一个端点在其下方.另一个端点在其上方的的返祖边可以

codeforces 505 D Mr. Kitayuta&#39;s Technology

题意:给出n个点,m条有向边,问构造一个新图,最少几条边可以让任意两点间的连通性跟原图一样. 做法:首先做出强连通分量,很显然对于有向图而言,若分图的点不唯一必定成环,当然啦,还需要做的是把这些分图再连起来变成弱连通分量,若某个弱连通分量的点数为v,若有环则贡献v条边,否则贡献v-1条边. #include<map> #include<string> #include<cstring> #include<cstdio> #include<cstdlib

CodeChef Counting on a directed graph

Counting on a directed graph Problem Code: GRAPHCNT All submissions for this problem are available. Read problems statements in Mandarin Chineseand Russian. Given an directed graph with N nodes (numbered from 1 to N) and M edges, calculate the number

Counting Inversion Pairs in an Array

Given an array, for example, 2 4 6 1 3 5, an inversion pair is the pair whose first value is larger than its second value according to the sequence from left to right, (2,1) (4,1) (4,3) (6,1) (6,3) (6,5). The following code is an O(nlog(n)) algorithm

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

51nod 1290 Counting Diff Pairs | 莫队 树状数组 题面 一个长度为N的正整数数组A,给出一个数K以及Q个查询,每个查询包含2个数l和r,对于每个查询输出从A[i]到A[j]中,有多少对数,abs(A[i] - A[j]) <= K(abs表示绝对值). 题解 莫队!//其实我就是搜索"51nod + 莫队"找到的这道题-- 七级算法题! 一道320分! 你值得拥有! 题解就是--用个普通的莫队,加上树状数组来统计符合条件的数个数,就好啦. 当增加/

51nod 1290 Counting Diff Pairs 莫队 + bit

一个长度为N的正整数数组A,给出一个数K以及Q个查询,每个查询包含2个数l和r,对于每个查询输出从A[i]到A[j]中,有多少对数,abs(A[i] - A[j]) <= K(abs表示绝对值). Input 第1行:3个数N,K,Q,中间用空格分隔,N为数组A的长度,K为差距,Q为查询的数量.(2 <= N <= 50000, 0 <= K <= 10^9, 1 <= Q <= 50000) 第2至N + 1行:每行1个数,对应数组中的数(1 <= A[i

codechef Scoring Pairs

难度 \(medium-hard\) 题意 官方中文题意 做法 很显然是可以通过计算常数个\(sum(A,B)=\sum\limits_{i=0}^A \sum\limits_{j=0}^B score(i,j)\) 结论1:\(score(i,j)\)为\(i,j\)数位拆分后排序的状态 暴力分类讨论或打表可得,不详述 设\(E\)为两数分数期望 则\(sum(A,B)=(A+1)(B+1)E\) 设\(x_{ij},y_{ij}\)分别为第一个数和第二个数排序后第\(i\)位为\(j\)的概

[LeetCode] Reverse Pairs 翻转对

Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j]. You need to return the number of important reverse pairs in the given array. Example1: Input: [1,3,2,3,1] Output: 2 Example2: Input: [2,4,3,5,1] Output:

Twitter OA prepare: even sum pairs

Write a function: class Solution { public int solution(int[] A); } that, given an array A consisting of N integers, returns the number of pairs (P, Q) such that 0 ≤ P < Q < N and (A[P] + A[Q]) is even. The function should return −1 if the number of