前言
这个应该早点学的!!
一、原理
比普通并查集强的是,带权并查集可以维护集合内任意两点的关系。
想象在一个一维数轴上,定义带权并查集中 a 到 b 的距离为 100,当且仅当 a 在 b 的右侧距离 100 的位置,即 a-b=100。之后,若有 c 到 d 的距离为 -20,就说明 c 在 d 左侧距离 20 的位置。此时,由于 a,b 和 c,d 不在一个集合中,所以是没法判断 a,b 和 c,d 的相对位置关系的。而若有了关系 a 到 c 距离为 -60,此时就可以合并两个集合,就可以求出任意两点的距离了。
在并查集中,维护好每个节点到头节点的距离 dis,头节点的 dis 为 0。在合并时,先查出 a 和 c 集合的代表节点 b 和 d,然后将 b 挂在 d 的下面。b 到 d 的距离就可以通过 c 到 d 的距离 disc 减去 a 到 b 的距离 disa 再加上 a 到 c 的距离得到。
在合并完以后,可以发现此时 disa 没有被修正对,这个数据就等到之后 find 做路径压缩时修正,只需要在父节点修正好的 dis 的基础上加上当前的 disa 即可。对于查询,就是 find 路径压缩后,直接用两节点的 dis 相减即可。
二、题目
1.推导部分和
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
struct Weighted_DSU{
vector<int>father;
vector<T>dis;
Weighted_DSU(int n):father(n),dis(n){
for(int i=0;i<n;i++){
father[i]=i;
dis[i]=0;
}
}
int find(int i){
if(i!=father[i]){
int tmp=father[i];
father[i]=find(tmp);
dis[i]+=dis[tmp];
}
return father[i];
}
void merge(int l,int r,T v){
int lf=find(l),rf=find(r);
if(lf!=rf){
father[lf]=rf;
dis[lf]=v+dis[r]-dis[l];
}
}
T query(int l,int r){
if(find(l)!=find(r)){
return INFLL;
}
return dis[l]-dis[r];
}
};
void solve()
{
int n,m,q;
cin>>n>>m>>q;
Weighted_DSU<ll>dsu(n+2);
for(int i=1;i<=m;i++)
{
ll l,r,v;
cin>>l>>r>>v;
dsu.merge(l,r+1,v);
}
int l,r;
while(q--)
{
cin>>l>>r;
ll res=dsu.query(l,r+1);
if(res==INFLL)
{
cout<<"UNKNOWN"<<endl;
}
else
{
cout<<res<<endl;
}
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这里要维护的是单点的范围累加和,那么就可以考虑转化成一维数轴的线段问题。那么对于 l,r 范围的累加和,就可以认为是数轴上 l,r+1 两点的线段距离。那么就是用带权并查集维护,每次查询即可,注意 r+1 的问题。
2.狡猾的商人
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
struct Weighted_DSU{
vector<int>father;
vector<T>dis;
Weighted_DSU(int n):father(n),dis(n){
for(int i=0;i<n;i++){
father[i]=i;
dis[i]=0;
}
}
int find(int i){
if(i!=father[i]){
int tmp=father[i];
father[i]=find(tmp);
dis[i]+=dis[tmp];
}
return father[i];
}
void merge(int l,int r,T v){
int lf=find(l),rf=find(r);
if(lf!=rf){
father[lf]=rf;
dis[lf]=v+dis[r]-dis[l];
}
}
T query(int l,int r){
if(find(l)!=find(r)){
return INF;
}
return dis[l]-dis[r];
}
bool check(int l,int r,T v)
{
if(find(l)==find(r))
{
if(dis[l]-dis[r]!=v)
{
return false;
}
}
return true;
}
};
void solve()
{
int n,m;
cin>>n>>m;
Weighted_DSU<ll>dsu(n+2);
int ok=1;
for(int i=1;i<=m;i++)
{
ll l,r,v;
cin>>l>>r>>v;
if(!dsu.check(l,r+1,v))
{
ok=0;
}
else
{
dsu.merge(l,r+1,v);
}
}
cout<<(ok?"true":"false")<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个和上一个题唯一不同的就是要判断矛盾。这个就只需要查当前 l,r+1 的值,如果在一个集合中,就说明当前可以确定两者的值。那么如果和给定的值对得上就不用管,对不上就说明矛盾了。
3.How Many Answers Are Wrong
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
struct Weighted_DSU{
vector<int>father;
vector<T>dis;
Weighted_DSU(int n):father(n),dis(n){
for(int i=0;i<n;i++){
father[i]=i;
dis[i]=0;
}
}
int find(int i){
if(i!=father[i]){
int tmp=father[i];
father[i]=find(tmp);
dis[i]+=dis[tmp];
}
return father[i];
}
void merge(int l,int r,T v){
int lf=find(l),rf=find(r);
if(lf!=rf){
father[lf]=rf;
dis[lf]=v+dis[r]-dis[l];
}
}
T query(int l,int r){
if(find(l)!=find(r)){
return INF;
}
return dis[l]-dis[r];
}
bool check(int l,int r,T v){
if(find(l)==find(r)){
if(dis[l]-dis[r]!=v){
return false;
}
}
return true;
}
};
void solve()
{
int n,m;
while(cin>>n>>m)
{
int ans=0;
Weighted_DSU<ll>dsu(n+2);
for(int i=1,l,r,v;i<=m;i++)
{
cin>>l>>r>>v;
if(!dsu.check(l,r+1,v))
{
ans++;
}
else
{
dsu.merge(l,r+1,v);
}
}
cout<<ans<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
几乎一样的题,没啥好说的。
4.银河英雄传说
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
struct Weighted_DSU{
vector<int>father;
vector<T>dis;
vector<int>size;
Weighted_DSU(int n):father(n),dis(n),size(n){
for(int i=0;i<n;i++){
father[i]=i;
dis[i]=0;
size[i]=1;
}
}
int find(int i){
if(i!=father[i]){
int tmp=father[i];
father[i]=find(tmp);
dis[i]+=dis[tmp];
}
return father[i];
}
void merge(int l,int r){
int lf=find(l),rf=find(r);
if(lf!=rf){
father[lf]=rf;
dis[lf]=size[rf];
size[rf]+=size[lf];
}
}
T query(int l,int r){
if(find(l)!=find(r)){
return -1;
}
return abs(dis[l]-dis[r])-1;
}
bool check(int l,int r,T v){
if(find(l)==find(r)){
if(dis[l]-dis[r]!=v){
return false;
}
}
return true;
}
};
const int MAXN=30000+5;
void solve()
{
int q;
cin>>q;
Weighted_DSU<int>dsu(MAXN);
string op;
int i,j;
while(q--)
{
cin>>op>>i>>j;
if(op=="M")
{
dsu.merge(i,j);
}
else if(op=="C")
{
int res=dsu.query(i,j);
cout<<res<<endl;
}
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
对于合并的操作,很明显可以想到用并查集解决。那么对于查询两艘战舰的距离,在合并时就可以将排在后面战舰的 dis 设置为父亲集合的大小,之后的战舰就可以对应修正对了。
5.除法求值
cpp
template<typename T>
struct Weighted_DSU{
map<string,string>father;
map<string,T>dis;
Weighted_DSU(int n,vector<vector<string>>& equations){
for(auto &vec:equations)
{
for(auto &str:vec)
{
father[str]=str;
dis[str]=1.0;
}
}
}
string find(string i){
if(father.find(i)==father.end())
{
return "";
}
if(i!=father[i]){
string tmp=father[i];
father[i]=find(tmp);
dis[i]*=dis[tmp];
}
return father[i];
}
void merge(string l,string r,T v){
string lf=find(l),rf=find(r);
if(lf!=rf){
father[lf]=rf;
dis[lf]=dis[r]/dis[l]*v;
}
}
T query(string l,string r){
string lf=find(l),rf=find(r);
if(lf==""||rf==""||lf!=rf){
return -1;
}
return dis[l]/dis[r];
}
bool check(string l,string r,T v){
if(find(l)==find(r)){
if(dis[l]/dis[r]!=v){
return false;
}
}
return true;
}
};
class Solution {
public:
vector<double> calcEquation(
vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries)
{
int n=equations.size();
Weighted_DSU<double>dsu(n,equations);
for(int i=0;i<n;i++)
{
dsu.merge(equations[i][0],equations[i][1],values[i]);
}
int m=queries.size();
vector<double>ans(m);
for(int i=0;i<m;i++)
{
ans[i]=dsu.query(queries[i][0],queries[i][1]);
}
return ans;
}
};
这个很明显就是带权并查集维护倍数关系,那么将加减改成乘除,之后套板子直接写即可。
6.食物链
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 3;
using Z = ModInt<M>;
template<typename T>
struct Weighted_DSU{
vector<int>father;
vector<T>dis;
Weighted_DSU(int n):father(n),dis(n){
for(int i=0;i<n;i++){
father[i]=i;
dis[i]=0;
}
}
int find(int i){
if(i!=father[i]){
int tmp=father[i];
father[i]=find(tmp);
dis[i]+=dis[tmp];
}
return father[i];
}
void merge(int l,int r,T v){
int lf=find(l),rf=find(r);
if(lf!=rf){
father[lf]=rf;
dis[lf]=v+dis[r]-dis[l];
}
}
T query(int l,int r){
if(find(l)!=find(r)){
return INF;
}
return dis[l]-dis[r];
}
bool check(int l,int r,T v){
if(find(l)==find(r)){
if(dis[l]-dis[r]!=v){
return false;
}
}
return true;
}
};
void solve()
{
int n,q;
cin>>n>>q;
Weighted_DSU<Z>dsu(n+1);
int ans=0;
for(int i=1,op,x,y;i<=q;i++)
{
cin>>op>>x>>y;
if(x>n||y>n||(op==2&&x==y))
{
ans++;
continue;
}
if(op==1)
{
if(!dsu.check(x,y,0))
{
ans++;
}
else
{
dsu.merge(x,y,0);
}
}
else if(op==2)
{
if(!dsu.check(x,y,1))
{
ans++;
}
else
{
dsu.merge(x,y,1);
}
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
有了前三个题的练习,对于这种维护关系判断真假的问题,很容易就能想到使用带权并查集。那么对于这个循环吃的问题,还是考虑转化成取模问题。所以就只需要用 dis 维护 0,1,2 三种类型,0 表示和父节点类型相同,1 表示吃父节点,2 表示被父节点吃。此时可以发现,这个关系也可以在模 3 意义下的距离模型成立,所以拿基本公式表示即可。
7.关押罪犯
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
struct DSU{
vector<int>father;
vector<int>siz;
//自定义
DSU(int n):father(n),siz(n){
for(int i=0;i<n;i++){
father[i]=i;
siz[i]=1;
}
}
int find(int i){
if(i!=father[i]){
father[i]=find(father[i]);
}
return father[i];
}
bool same(int x,int y){
return find(x)==find(y);
}
bool merge(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx==fy){
return false;
}
father[fx]=fy;
siz[fy]+=siz[fx];
return true;
}
};
void solve()
{
int n,m;
cin>>n>>m;
vector<array<int,3>>a(m+1);
for(int i=1;i<=m;i++)
{
cin>>a[i][0]>>a[i][1]>>a[i][2];
}
sort(a.begin()+1,a.end(),[&](auto &x,auto &y)
{
return x[2]>y[2];
});
DSU dsu(n+1);
vector<int>op(n+1);
for(int i=1;i<=m;i++)
{
auto [x,y,v]=a[i];
if(dsu.same(x,y))
{
cout<<v<<endl;
return ;
}
if(op[x]==0)
{
op[x]=y;
}
else
{
dsu.merge(op[x],y);
}
if(op[y]==0)
{
op[y]=x;
}
else
{
dsu.merge(x,op[y]);
}
}
cout<<0<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
由于是要让最大的冲突值最小,那么就可以考虑从大到小贪心地考虑,每次尽量让当前的冲突不满足,这样找到的第一个不得不满足的冲突值就是答案。那么就可以维护之前每个人 x 发生冲突的对象 y,对于后续的冲突 (x,z),就是让 z 和 y 处于一个监狱即可,这个可以用普通并查集维护。
8.团伙 (Day 2)
感觉遇到这种分类问题就要考虑用并查集维护。
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
struct DSU{
vector<int>father;
vector<int>siz;
//自定义
DSU(int n):father(n),siz(n){
for(int i=0;i<n;i++){
father[i]=i;
siz[i]=1;
}
}
int find(int i){
if(i!=father[i]){
father[i]=find(father[i]);
}
return father[i];
}
bool same(int x,int y){
return find(x)==find(y);
}
bool merge(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx==fy){
return false;
}
father[fx]=fy;
siz[fy]+=siz[fx];
return true;
}
};
void solve()
{
int n,m;
cin>>n>>m;
DSU dsu(n+1);
vector<int>enemy(n+1);
string op;
int x,y;
for(int i=1;i<=m;i++)
{
cin>>op>>x>>y;
if(op=="E")
{
if(enemy[x]==0)
{
enemy[x]=y;
}
else
{
dsu.merge(enemy[x],y);
}
if(enemy[y]==0)
{
enemy[y]=x;
}
else
{
dsu.merge(x,enemy[y]);
}
}
else if(op=="F")
{
dsu.merge(x,y);
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(i==dsu.find(i))
{
ans++;
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
跟上个题一样,就是最后看有多少个集合即可。
9.Exclusive-OR
傻逼杭电在输入恶心人......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
struct Weighted_DSU{
vector<int>father;
vector<T>dis;
int n;
Weighted_DSU(int _n):n(_n),father(_n+1),dis(_n+1){
for(int i=0;i<=n;i++){
father[i]=i;
dis[i]=0;
}
}
int find(int i){
if(i!=father[i]){
int tmp=father[i];
father[i]=find(tmp);
dis[i]^=dis[tmp];
}
return father[i];
}
bool merge(int l,int r,T v){
int lf=find(l),rf=find(r);
if(lf==rf){
if((dis[l]^dis[r])!=v){
return false;
}
}else{
if(lf==n)
{
swap(lf,rf);
}
father[lf]=rf;
dis[lf]=v^dis[r]^dis[l];
}
return true;
}
T query(int l,int r){
if(find(l)!=find(r)){
return INF;
}
return dis[l]^dis[r];
}
};
void solve()
{
int t=0;
int n,m;
cin>>n>>m;
while(n!=0&&m!=0)
{
cout<<"Case "<<(++t)<<":"<<endl;
int ok=1;
int cnti=0;
Weighted_DSU<int>dsu(n);
for(int _=1;_<=m;_++)
{
string op;
cin>>op;
if(op=="I")
{
string s;
getline(cin,s);
array<int,3>a={0};
int cnta=0;
for(auto ch:s)
{
if(ch==' ')
{
cnta++;
}
else if('0'<=ch&&ch<='9')
{
a[cnta-1]=a[cnta-1]*10+ch-'0';
}
}
if(ok)
{
int l,r,v;
if(cnta==2)
{
l=a[0],v=a[1],r=n;
}
else if(cnta==3)
{
l=a[0],r=a[1],v=a[2];
}
cnti++;
if(!dsu.merge(l,r,v))
{
ok=0;
cout<<"The first "<<cnti<<" facts are conflicting."<<endl;
}
}
}
else if(op=="Q")
{
int len;
cin>>len;
vector<int>a(len+1);
for(int i=1;i<=len;i++)
{
cin>>a[i];
}
auto proc=[&]()->int
{
int ans=0;
map<int,int>mp;
for(int i=1;i<=len;i++)
{
mp[dsu.find(a[i])]++;
ans^=dsu.dis[a[i]];
}
for(auto [k,v]:mp)
{
if(k!=n&&v%2)
{
return -1;
}
}
return ans;
};
if(ok)
{
int res=proc();
if(res==-1)
{
cout<<"I don't know."<<endl;
}
else
{
cout<<res<<endl;
}
}
}
}
cout<<endl;
cin>>n>>m;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个题和之前不一样的是,这个题会给出一个绝对数值,不仅仅只有相对关系。那么对于这个问题,联系之前的差分约束,可以想到设置一个虚点 0,利用 v^0=v 这个性质,将绝对数值关系转化成相对关系处理。注意不能让这个虚点挂在别的节点下面!
对于查询操作,若查询的节点是偶数个,那么其 dis 的异或和就可以将这个公共的头节点消掉,所以答案就是所有 dis 的异或和。而若节点是奇数个,那么 dis 的异或和就会多一个头节点,那么就没法确定最后的答案。而对于头节点是虚点的情况,就不存在奇偶的限制了。
特殊的,此时就不需要判断所有点是否都在一个集合中了。对于涉及到的若干个集合,若各个集合单独的答案都是可以确定的,那么都异或起来就是最终答案。也就是说,只有当某个非虚点的头节点出现了奇数次,那么就是无解的。
总结
加快学习速度!!