[JSOI2008]星球大战
非常套路的,正着打击星球,逆着就是添加星球以及关系,并查集维护此时连通块个数
就是这个星球被打击前的答案
#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
转化成最短路问题,套用 d i j k s t r a \rm dijkstra dijkstra,每个点都只访问一次,但是范围那么大,用并查集帮助跳过已访问点,直指新点
#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;
}
方格染色
observation1:
只要确定了第一行和第一列,整张表格就被确定了
observation2:
g
1
,
1
⨁
g
i
,
1
⨁
g
1
,
j
⨁
g
i
,
j
=
[
i
m
o
d
2
=
0
a
n
d
j
m
o
d
2
=
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]
可以通过枚举 g 1 , 1 g_{1,1} g1,1的状态,通过已知的 g i , j g_{i,j} gi,j来判断 g i , 1 , g 1 , j g_{i,1},g_{1,j} gi,1,g1,j的关系
这样形成了若干组有且仅有两个选项的约束关系,带权并查集维护
连通块内有一个取值定了,其他的取值也定了
如果有解,答案就是2^(连通块数量-1)
,减1是
g
1
,
1
g_{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 blue
for( int i = 1;i <= k;i ++ ) c[i] ^= 1;//c[g[1][1]]=1 red
int ans1 = solve();
if( flag0 ) ans1 = 0;
if( flag1 ) ans0 = 0;
printf( "%lld\n", ( ans0 + ans1 ) % mod );
return 0;
}
Junk-Mail Filter
可删除并查集模板
只需要注意一下被删除前的集合若只有一个,元素被删除后集合也就不存在了
#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 提高组] 关押罪犯
贪心,先把冲突最大的安排,可以选择拆点 i , i + n i,i+n i,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 );
else
merge( u, v + n ), merge( v, u + n );
}
printf( "0\n" );
return 0;
}
Silver Woods
跟一道奶酪题相似,奶酪是空心球,老鼠从底到顶;这道题是从左到右
二分半径,然后将圆卡不过的连接起来
具体而言就是点点之间距离小于直径,点和上下界面距离小于直径,合并起来
并查集判断上界面和下界面是否相通,不通证明有一种方法圆可以通过
#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!
对于每个坐标 ( x i , y i ) (xi,yi) (xi,yi), x i xi xi向 y i + n yi+n yi+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;
}