Codeforces Round #812 (Div. 2)

Codeforces Round #812 (Div. 2)

E. Cross Swapping (扩展域并查集解决2-SAT)


引用一段关于扩展域并查集的总结:

并查集分两种:带边权和扩展域

带边权:带边权的并查集维护的是相对关系,也就是相对于根的关系, 能用并查集来做个核心思想有两点,第一点就是关系具有传递性, 也就是说,如果已知 x , y x,y x,y 的关系也知道 y , z y,z y,z 的关系,那么必然知道 x , z x,z x,z 的关系第二点是要必须具有对称性,无向边 a a a 能到 b b b 同时 b b b 也能到 a a a

扩展域:维护的是多组关系。怎么理解呢?这里有一种理解的方式就是:假如存在 x x x 种性质,一共有 n n n 个节点,那么对于每个物品 y y y 来说 f [ y ] f[y] f[y] 代表 y y y 是第一种性质, f [ y + n ] f[y + n] f[y+n]代表 y y y 是第二种性质 … f [ y + ( x − 1 ) n ] f[y + (x -1)n] f[y+(x1)n] 代表 y y y 是第 x x x 种性质。这不就是SAT吗

​ 如果处理关系和矛盾?以关押罪犯这道题为例,首先定义 f [ x ] f[x] f[x] 是犯人 x x x 所在的监狱, f [ x + n ] f[x + n] f[x+n] 是另一座监狱,如果这个时候我们要维护 a , b a, b a,b 不在一个监狱。就先判断如果 p [ f [ a ] ] = = p [ f [ b ] ] p[f[a]] == p[f[b]] p[f[a]]==p[f[b]] 那么就矛盾,否则就把 p [ f [ a ] ] = p [ f [ n + b ] ] p[f[a]] = p[f[n + b]] p[f[a]]=p[f[n+b]], p [ f [ b ] ] = p [ f [ n + a ] ] p[f[b]] = p[f[n + a]] p[f[b]]=p[f[n+a]] 意思就是把a所在的监狱与b不在的监狱合并,同时把 b b b 所在的监狱与 a a a 不在的监狱合并。


每个点进行转置只有两个选择,换 or 不换。要求字典序最小,从前往后贪心。

如果一个点一个点考虑就会麻烦,一行一行的考虑。

i i i 表示换, i + n i+n i+n 表不换。本题需要快读 or scanf

最后找一个起始点,非常巧妙。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <sstream>
#define pb push_back 
#define all(x) (x).begin(),(x).end()
#define mem(f, x) memset(f,x,sizeof(f)) 
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math,O3")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

typedef pair<int,int>PII;
typedef pair<long,long>PLL;

typedef long long ll;
typedef unsigned long long ull; 
const int N=2e6+10,M=1e9+7;
ll n,m,_;

int a[1100][1100];
int fa[2200];

int find(int x){
    return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}

void solve(){

	cin>>n;
    fo(i,1,n*2)fa[i] = i;
    fo(i,1,n)fo(j,1,n)cin>>a[i][j];
    fo(i,1,n){
        fo(j,i+1,n){
            if(a[i][j] > a[j][i]){
                int x = i,y = j;
                int nx = x + n,ny = y + n;
                if(find(nx) == find(ny)){
                    continue;
                }
                fa[find(x)] = find(ny);
                fa[find(nx)] = find(y);
            }
            else if (a[i][j] < a[j][i]){
                int x = i,y = j;
                int nx = x + n,ny = y + n;
                if(find(x) == find(ny)){
                    continue;
                }
                fa[find(x)] = find(y);
                fa[find(nx)] = find(ny);
            }
        }
    }
    set<int>st;
    fo(i,1,n){
        if(fa[i] == i)st.insert(i);
    }
	// i 是换的起始点 , st.size()一定为1
    fo(i,1,n){
        if(st.count(find(i))){ // 注意一定是find(i)
            fo(j,1,n){
                swap(a[i][j],a[j][i]);
            }
        }
    }
    fo(i,1,n){
        fo(j,1,n){
            cout<<a[i][j]<<" ";
        }
        cout<<endl;
    }
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

D. Tournament Countdown


需要一些灵感 or 套路的题。

场次要在 2 3 2 n {\frac{2}{3}2^n} 322n

考虑:一共 2 n 2^n 2n 人,需要删掉 2 n − 1 2^n-1 2n1 人,想到 ?套路 两次可以从4个人中删掉3个人。

2 ( 2 n − 1 ) 3 < 2 3 2 n \frac{2(2^n-1)}{3} < \frac{2}{3}2^n 32(2n1)<322n

之后就是如何做到两次从4个人里边询问胜者。

vKpM4A.png


非常巧妙,询问 1 3。

如果 1 > 3 , 又因为1和2要竞争1大于3的前提是1>2,所以淘汰了2,3。

如果 1 < 3 , 同上淘汰了 1,4。

如果 1 == 3 , 淘汰 1,3。


/*
A: 10min
B: 20min
C: 30min
D: 40min
*/ 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <sstream>
#define pb push_back 
#define all(x) (x).begin(),(x).end()
#define mem(f, x) memset(f,x,sizeof(f)) 
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math,O3")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
template<typename T1,typename T2>
ostream& operator<<(ostream& os,const map<T1,T2>&v){for(auto c:v)os<<c.first<<" "<<c.second<<endl;return os;}
template<typename T>inline void rd(T &a) {
    char c = getchar(); T x = 0, f = 1; while (!isdigit(c)) {if (c == '-')f = -1; c = getchar();}
    while (isdigit(c)) {x = (x << 1) + (x << 3) + c - '0'; c = getchar();} a = f * x;
}

typedef pair<int,int>PII;
typedef pair<long,long>PLL;

typedef long long ll;
typedef unsigned long long ull; 
const int N=2e6+10,M=1e9+7;
ll n,m,_;

int ask(int x,int y){
    cout<<"? "<<x<<" "<<y<<endl;
    int res;cin>>res;
    return res;
}

void solve(){
    cin>>n;
    n = 1<<n;
    vector<int>a;
    fo(i,1,n)a.pb(i);

    // 4个四个询问
    while(a.size()>1){
        vector<int>b;
        if (a.size() == 2) {
            int t = ask(a[0], a[1]);
            if (t == 1)
                b.push_back(a[0]);
            else
                b.push_back(a[1]);
        }
        else{
	        for(int i=0;i<a.size();i+=4){
	            int x = a[i];
	            int y = a[i+2];
//	            debug(x);
//	            debug(y);
	            int res = ask(x,y);
	            if(res == 1){
	                res = ask(x,a[i+3]);
	                if(res == 1){
	                	res = x;
	                }
	                else res = a[i+3];
	            }
	            else if(res == 2){
	                res = ask(a[i+1],y);
	                if(res == 1){
	                	res = a[i+1];
	                }
	                else res = y;
	            }
	            else{
	                res = ask(a[i+1],a[i+3]);
	                if(res == 1){
	                	res = a[i+1];
	                }
	                else res = a[i+3];
	            }
	            b.pb(res);
	        }
	    }
        a = b;
    }
    cout<<"! "<<a[0]<<endl;
}

int main(){
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值