1. 程式人生 > >Counting The Important Pairs CodeChef - TAPAIR

Counting The Important Pairs CodeChef - TAPAIR

names 至少 ctime 。。 提示 tac bridge 繼續 long

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.


Counting The Important Pairs CodeChef - TAPAIR