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