专题突破三之并查集Ⅱ——星球大战,In Touch,方格染色,Junk-Mail Filter,关押罪犯,Silver Woods,Must Be Rectangular!
文章目錄
- [JSOI2008]星球大戰
- In Touch
- 方格染色
- Junk-Mail Filter
- [NOIP2010 提高組] 關押罪犯
- Silver Woods
- Must Be Rectangular!
[JSOI2008]星球大戰
source
非常套路的,正著打擊星球,逆著就是添加星球以及關系,并查集維護此時連通塊個數
就是這個星球被打擊前的答案
#include <cstdio> #include <vector> #include <iostream> using namespace std; #define maxn 400005 vector < int > h[maxn]; pair < int, int > p[maxn]; int n, m, k, ans; int f[maxn], g[maxn], ret[maxn]; bool used[maxn], vis[maxn];void makeset() {for( int i = 0;i < n;i ++ ) f[i] = i; }int find( int x ) {return x == f[x] ? x : f[x] = find( f[x] ); }void merge( int u, int v ) {u = find( u ), v = find( v );if( u ^ v ) ans --, f[v] = u; }int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= m;i ++ ) {scanf( "%d %d", &p[i].first, &p[i].second );h[p[i].first].push_back( i );h[p[i].second].push_back( i );}scanf( "%d", &k );makeset();for( int i = 1;i <= k;i ++ )scanf( "%d", &g[i] ), used[g[i]] = 1;ans = n - k;for( int i = 1;i <= m;i ++ )if( ! used[p[i].first] and ! used[p[i].second] )merge( p[i].first, p[i].second );for( int i = k;i;i -- ) {ret[i] = ans;ans ++;used[g[i]] = 0;for( auto t : h[g[i]] )if( vis[t] ) continue;else if( used[p[t].first] or used[p[t].second] )continue;else merge( p[t].first, p[t].second );}printf( "%d\n", ans );for( int i = 1;i <= k;i ++ )printf( "%d\n", ret[i] );return 0; }In Touch
source
轉化成最短路問題,套用dijkstra\rm dijkstradijkstra,每個點都只訪問一次,但是范圍那么大,用并查集幫助跳過已訪問點,直指新點
#include <iostream> #include <cstdio> #include <queue> using namespace std; #define Pair pair < int, int > #define int long long #define maxn 200005 #define inf 1e15 priority_queue < Pair, vector < Pair >, greater < Pair > > q; int T, n; int dis[maxn], f[maxn], L[maxn], R[maxn], c[maxn]; int MS[2] = { -1, 1 };int find( int x ) {return x == f[x] ? x : f[x] = find( f[x] ); }signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &L[i] );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &R[i] );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &c[i] );for( int i = 0;i <= n + 1;i ++ ) f[i] = i, dis[i] = inf;q.push( make_pair( dis[1] = c[1], 1 ) );while( ! q.empty() ) {int now = q.top().second; q.pop();for( int k = 0;k < 2;k ++ ) {int l = now + L[now] * MS[k];int r = now + R[now] * MS[k];if( l > r ) swap( l, r );l = min( l, n + 1 );l = max( l, 1ll );if( l > r ) continue;for( int nxt = l;;nxt ++ ) {nxt = find( nxt );if( nxt <= 0 || nxt > n || nxt > r ) break;if( dis[nxt] > dis[now] + c[nxt] ) {dis[nxt] = dis[now] + c[nxt];q.push( make_pair( dis[nxt], nxt ) );}f[find( nxt )] = find( nxt + 1 );}}}printf( "0" );for( int i = 2;i <= n;i ++ )if( dis[i] == inf ) printf( " -1" );else printf( " %lld", dis[i] - c[i] );printf( "\n" );}return 0; }方格染色
source
observation1: 只要確定了第一行和第一列,整張表格就被確定了
observation2: g1,1?gi,1?g1,j?gi,j=[imod2=0andjmod2=0]g_{1,1}\bigoplus g_{i,1}\bigoplus g_{1,j}\bigoplus g_{i,j}=[i\ \rm mod\ 2=0\ and\ j\ mod\ 2=0]g1,1??gi,1??g1,j??gi,j?=[i?mod?2=0?and?j?mod?2=0]
可以通過枚舉g1,1g_{1,1}g1,1?的狀態,通過已知的gi,jg_{i,j}gi,j?來判斷gi,1,g1,jg_{i,1},g_{1,j}gi,1?,g1,j?的關系
這樣形成了若干組有且僅有兩個選項的約束關系,帶權并查集維護
連通塊內有一個取值定了,其他的取值也定了
如果有解,答案就是2^(連通塊數量-1),減1是g1,1g_{1,1}g1,1?的狀態因為是枚舉的,算已知的
#include <cstdio> #define int long long #define mod 1000000000 #define maxn 2000005 int n, m, k; bool flag0, flag1; int x[maxn], y[maxn], c[maxn], f[maxn], w[maxn];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans; }int find( int x ) {if( x == f[x] ) return x;else {int fa = f[x];f[x] = find( f[x] );w[x] = ( w[x] + w[fa] ) % 2;return f[x];} }int solve() {for( int i = 1;i <= n + m;i ++ ) f[i] = i, w[i] = 0;f[n + 1] = 1;for( int i = 1;i <= k;i ++ ) {if( x[i] == 1 && y[i] == 1 ) continue;int u = find( x[i] ), v = find( y[i] + n );int t = w[x[i]] ^ w[y[i] + n] ^ c[i] ^ ( x[i] & 1 or y[i] & 1 ) ^ 1;if( u == v and t ) return 0;f[v] = u, w[v] = t;} int cnt = 0;for( int i = 1;i <= n + m;i ++ )if( find( i ) == i ) cnt ++;return qkpow( 2, cnt - 1 ); }signed main() {scanf( "%lld %lld %lld", &n, &m, &k );for( int i = 1;i <= k;i ++ ) {scanf( "%lld %lld %lld", &x[i], &y[i], &c[i] );if( x[i] == 1 && y[i] == 1 )if( ! c[i] ) flag0 = 1;else flag1 = 1;else;}int ans0 = solve();//c[g[1][1]]=0 bluefor( int i = 1;i <= k;i ++ ) c[i] ^= 1;//c[g[1][1]]=1 redint ans1 = solve();if( flag0 ) ans1 = 0;if( flag1 ) ans0 = 0;printf( "%lld\n", ( ans0 + ans1 ) % mod );return 0; }Junk-Mail Filter
source
可刪除并查集模板
只需要注意一下被刪除前的集合若只有一個,元素被刪除后集合也就不存在了
#include <cstdio> #include <iostream> using namespace std; #define maxn 2200000 int n, m, ans, cnt; int f[maxn], siz[maxn];void makeset() {cnt = n;for( int i = 0;i < n;i ++ )f[i] = f[i + n] = cnt ++, siz[i + n] = 1; }int find( int x ) {return x == f[x] ? x : f[x] = find( f[x] ); }void merge( int u, int v ) {u = find( u ), v = find( v );if( siz[u] < siz[v] ) swap( u, v );if( u ^ v )siz[u] += siz[v], f[v] = u, ans --; }void Delete( int x ) {int fx = find( x );siz[fx] --;if( ! siz[fx] ) ans --;f[x] = f[cnt] = cnt;siz[cnt] = 1;cnt ++;ans ++; }int main() {int T = 0;while( scanf( "%d %d", &n, &m ) ) {if( ! n and ! m ) return 0;makeset();ans = n;char opt[5]; int x, y;for( int i = 1;i <= m;i ++ ) {scanf( "%s", opt );if( opt[0] == 'M' ) {scanf( "%d %d", &x, &y );merge( x, y );}else {scanf( "%d", &x );Delete( x );}}printf( "Case #%d: %d\n", ++ T, ans );}return 0; }[NOIP2010 提高組] 關押罪犯
source
貪心,先把沖突最大的安排,可以選擇拆點i,i+ni,i+ni,i+n,兩個人不同合并(i,j+n)/(i+n,j)(i,j+n)/(i+n,j)(i,j+n)/(i+n,j)
也可以選擇帶權并查集在%2\% 2%2意義下做
#include <cstdio> #include <algorithm> using namespace std; #define maxn 100005 struct node {int u, v, w;node(){}node( int U, int V, int W ) {u = U, v = V, w = W;} }r[maxn]; int n, m; int f[maxn];void makeset() {for( int i = 1;i <= ( n << 1 );i ++ ) f[i] = i; }int find( int x ) {return x == f[x] ? x : f[x] = find( f[x] ); }void merge( int u, int v ) {u = find( u ), v = find( v ), f[v] = u; }int main() {scanf( "%d %d", &n, &m );for( int i = 1, u, v, w;i <= m;i ++ ) {scanf( "%d %d %d", &u, &v, &w );r[i] = node( u, v, w );}makeset();sort( r + 1, r + m + 1, []( node x, node y ) { return x.w > y.w; } );for( int i = 1;i <= m;i ++ ) {int u = r[i].u, v = r[i].v;if( find( u ) == find( v ) )return ! printf( "%d\n", r[i].w );elsemerge( u, v + n ), merge( v, u + n );}printf( "0\n" );return 0; }Silver Woods
source
跟一道奶酪題相似,奶酪是空心球,老鼠從底到頂;這道題是從左到右
二分半徑,然后將圓卡不過的連接起來
具體而言就是點點之間距離小于直徑,點和上下界面距離小于直徑,合并起來
并查集判斷上界面和下界面是否相通,不通證明有一種方法圓可以通過
#include <cstdio> #include <cmath> #define eps 1e-6 #define maxn 105 int n; double x[maxn], y[maxn]; int f[maxn];void makeset() {for( int i = 0;i <= n + 1;i ++ ) f[i] = i; }int find( int x ) {return x == f[x] ? x : f[x] = find( f[x] ); }void merge( int u, int v ) {u = find( u ), v = find( v ), f[v] = u; }double dis( int i, int j ) {return sqrt( ( x[i] - x[j] ) * ( x[i] - x[j] ) + ( y[i] - y[j] ) * ( y[i] - y[j] ) ); }bool check( double r ) {double d = r * 2;makeset();for( int i = 1;i <= n;i ++ ) {if( y[i] + d >= 100 )merge( 0, i );if( y[i] - d <= -100 )merge( n + 1, i );for( int j = i + 1;j <= n;j ++ )if( dis( i, j ) < d )merge( i, j );}if( find( 0 ) == find( n + 1 ) ) return 0;else return 1; }int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%lf %lf", &x[i], &y[i] );double l = 0, r = 100, ans;while( r - l > eps ) {double mid = ( l + r ) / 2;if( check( mid ) ) ans = mid, l = mid;else r = mid;}printf( "%.6f\n", ans );return 0; }Must Be Rectangular!
source
對于每個坐標(xi,yi)(xi,yi)(xi,yi),xixixi向yi+nyi+nyi+n連邊,形成一個二分圖
然后后續添加點實際上就是對這個二分圖的每個分量補成完全二分圖
并查集維護一下二分圖的邊的個數,兩個部的點的個數
#include <cstdio> #define maxn 100000 int n; int f[maxn << 2], row[maxn << 2], col[maxn << 2];void makeset() {for( int i = 1;i <= ( maxn << 1 );i ++ )f[i] = i; }int find( int x ) {return x == f[x] ? x : f[x] = find( f[x] ); }void merge( int u, int v ) {u = find( u ), v = find( v ), f[v] = u; }int main() {scanf( "%d", &n );makeset();for( int i = 1, x, y;i <= n;i ++ ) {scanf( "%d %d", &x, &y );merge( x, y + maxn );}long long ans = 0;for( int i = 1;i <= maxn;i ++ ) row[find( i )] ++;for( int i = 1;i <= maxn;i ++ ) col[find( i + maxn )] ++;for( int i = 1;i <= maxn;i ++ ) ans += 1ll * row[i] * col[i];printf( "%lld\n", ans - n );return 0; }{
u = find( u ), v = find( v ), f[v] = u;
}
int main() {
scanf( “%d”, &n );
makeset();
for( int i = 1, x, y;i <= n;i ++ ) {
scanf( “%d %d”, &x, &y );
merge( x, y + maxn );
}
long long ans = 0;
for( int i = 1;i <= maxn;i ++ ) row[find( i )] ++;
for( int i = 1;i <= maxn;i ++ ) col[find( i + maxn )] ++;
for( int i = 1;i <= maxn;i ++ ) ans += 1ll * row[i] * col[i];
printf( “%lld\n”, ans - n );
return 0;
}
總結
以上是生活随笔為你收集整理的专题突破三之并查集Ⅱ——星球大战,In Touch,方格染色,Junk-Mail Filter,关押罪犯,Silver Woods,Must Be Rectangular!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新封神榜演员表 新封神榜演员有谁
- 下一篇: 如何申请支付宝账号 申请支付宝账号方法