AtCoder Beginner Contest 451(ABCDEFG)

前言

D 和 E 阴到没边了......

一、A - illegal

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<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 ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
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};

void solve()
{
    string s;
    cin>>s;
    int n=s.length();

    if(n%5)
    {
        No;
    }
    Yes;
}

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;
}

没啥好说的,判断直接输出即可。

二、B - Personnel Change

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<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 ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
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};

void solve()
{
    int n,m;
    cin>>n>>m;
    vector<int>cnts(m+1);
    for(int i=1,a,b;i<=n;i++)
    {
        cin>>a>>b;
        cnts[b]++;
        cnts[a]--;
    }

    for(int i=1;i<=m;i++)
    {
        cout<<cnts[i]<<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;
}

没啥好说的,因为原本位置每有一个数,就会对原来答案产生 -1 的贡献。而去往位置每有一个数,就会对答案产生 +1 的贡献,所以每次统计一下贡献输出即可。

三、C - Understory

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<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 ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
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};

void solve()
{
    int q;
    cin>>q;

    map<int,int>cnts;
    int sum=0;

    int op,h;
    while(q--)
    {
        cin>>op>>h;
        if(op==1)
        {
            cnts[h]++;
            sum++;
        }
        else
        {
            vector<int>del;
            for(auto [k,v]:cnts)
            {
                if(k>h)
                {
                    break;
                }

                sum-=cnts[k];
                del.push_back(k);
            }

            for(auto k:del)
            {
                cnts.erase(k);
            }
        }

        cout<<sum<<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;
}

因为每个数会被添加一次,被删除一次,所以就可以每次暴力删除,复杂度是 O(n)。那么考虑用一个 map 来维护,每次暴力删除即可。

四、D - Concat Power of 2

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<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 ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
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};

void solve()
{
    int n;
    cin>>n;

    const int MAXN=30;

    vector<ll>pow(MAXN);
    vector<ll>len(MAXN);
    for(int i=0;i<MAXN;i++)
    {
        pow[i]=1<<i;
        len[i]=to_string(pow[i]).length();
    }

    vector<ll>bit(10);
    bit[0]=1;
    for(int i=1;i<10;i++)
    {
        bit[i]=10*bit[i-1];
    }

    vector<set<ll>>nums(10);
    nums[0].insert(0);
    for(int i=1;i<=9;i++)
    {
        for(int j=0;j<MAXN;j++)
        {
            if(len[j]>i)
            {
                break;
            }

            for(auto x:nums[i-len[j]])
            {
                nums[i].insert(x*bit[len[j]]+pow[j]);
            }
        }
    }

    set<ll>res;
    for(int i=1;i<=9;i++)
    {
        for(auto x:nums[i])
        {
            res.insert(x);
        }
    }

    for(auto x:res)
    {
        if(--n==0)
        {
            cout<<x<<endl;
            return ;
        }
    }
}

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;
}

沟槽的 Attention is all you need......

观察样例三,注意到当好数逼近 10^9 时,此时 n 的规模是 10^6。那么其实是可以暴力搜,反正最多不超过 10^6 个。

考虑根据长度暴力搜,那么就是一个类似 dp 的过程。对于当前长度 i,枚举每个 2 的幂。若当前幂的长度为 j,那么就可以拼在所有长度为 i-j 的数转移过来。

五、E - Tree Distance

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<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 ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
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;
    //自定义

    DSU(int n){
        father.assign(n,0);
        for(int i=0;i<n;i++){
            father[i]=i;

        }
    }

    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;
        
        return true;
    }
};

void solve()
{
    int n;
    cin>>n;
    vector<vector<ll>>a(n+1,vector<ll>(n+1));
    vector<array<ll,3>>edge;
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            cin>>a[i][j];
            edge.push_back({i,j,a[i][j]});
        }
    }

    sort(edge.begin(),edge.end(),[&](auto &x,auto &y)
    {
        return x[2]<y[2];
    }); 

    DSU dsu(n+1);
    vector<vector<pll>>g(n+1);
    for(auto [u,v,w]:edge)
    {
        if(!dsu.same(u,v))
        {
            dsu.merge(u,v);
            g[u].push_back({v,w});
            g[v].push_back({u,w});
        }
    }

    vector<vector<ll>>ans(n+1,vector<ll>(n+1));

    for(int i=1;i<=n;i++)
    {
        auto dfs=[&](auto &&self,int u,int fa,ll dis)->void
        {
            ans[i][u]=dis;
            for(auto [v,w]:g[u])
            {
                if(v!=fa)
                {
                    self(self,v,u,dis+w);
                }
            }
        };

        dfs(dfs,i,0,0);
    }

    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(ans[i][j]!=a[i][j])
            {
                No;
            }
        }
    }
    Yes;
}

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;
}

纯你妈 guess 啊......

注意到在暴力按给定关系连边后,若树存在,那么其一定是这张图的最小生成树。

考虑证明这个结论。

运用反证法,假设该合法的树不是最小生成树。那么在 Kruskal 构建最小生成树的过程中,对于当前要选的边 (u,v),其必然是连接 u 和 v 两个连通块权值最小的边。那么如果不选这条边而是选择 (x,y) 这条边来连接这两个连通块,则必有权值 ,也就是有 。那么因为此时选择了 (x,y) 这条边,且边权非负,所以就导致从 u 到 v 的路径必然大于从 x 到 y 的路径,也就是有 。这是矛盾的,所以可以说明合法的树必然是最小生成树。

六、F - Make Bipartite 3

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<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 ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
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>sz;
    vector<int>color;
    vector<int>cnts;
    vector<vector<int>>child;

    DSU(int n){
        father.assign(n,0);
        sz.assign(n,1);
        color.assign(n,0);
        cnts.assign(n,0);
        child.assign(n,vector<int>(0));
        for(int i=0;i<n;i++){
            father[i]=i;
            child[i].push_back(i);
        }
    }

    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 fx,int fy){

        father[fx]=fy;
        sz[fy]+=sz[fx];
        for(auto u:child[fx])
        {
            color[u]^=1;
            if(color[u])
            {
                cnts[fy]++;
            }
            child[fy].push_back(u);
        }
        
        return true;
    }
};

void solve()
{
    int n,q;
    cin>>n>>q;

    int ans=0;
    DSU dsu(n+1);

    int u,v;
    while(q--)
    {
        cin>>u>>v;

        if(ans==-1)
        {
            cout<<-1<<endl;
            continue;
        }

        if(dsu.same(u,v))
        {
            if(dsu.color[u]==dsu.color[v])
            {
                ans=-1;
            }
        }
        else
        {
            int fu=dsu.find(u);
            int fv=dsu.find(v);

            ans-=min(dsu.cnts[fu],dsu.sz[fu]-dsu.cnts[fu]);
            ans-=min(dsu.cnts[fv],dsu.sz[fv]-dsu.cnts[fv]);

            int chg=(dsu.color[u]==dsu.color[v]);

            if(dsu.sz[fu]>dsu.sz[fv])
            {
                swap(fu,fv);
            }

            for(auto x:dsu.child[fu])
            {
                dsu.color[x]^=chg;
                if(dsu.color[x])
                {
                    dsu.cnts[fv]++;
                }
                dsu.child[fv].push_back(x);
            }

            dsu.father[fu]=fv;
            dsu.sz[fv]+=dsu.sz[fu];

            ans+=min(dsu.cnts[fv],dsu.sz[fv]-dsu.cnts[fv]);
        }

        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;
}

这就是启发式合并嘛,感觉以前是见过的。

首先不难想到,因为每次只能加边,所以若当前图不是个二分图了,之后不管怎么操作,这张图就都不可能是二分图了。

首先可以发现,对于同一个连通块内的点,其染色情况是可以任意翻转的。那么由于要求黑色的个数最少,所以就是连通块内黑色和白色的点数取最小。所以若黑色点的个数是 cnt,连通块的大小是 size,那么答案就是 min(cnt,size-cnt)。所以可以发现,不管如何翻转,最终答案都是不变的。

所以,考虑用并查集维护出当前所有连通块内的染色情况,然后对当前要连的边 (u,v) 进行讨论。

第一种情况,若原本 u 和 v 在同一个连通块内,那么若 u 和 v 同色,则此时就必然不合法了。否则,就什么也不用做即可。

第二种情况,若 u 和 v 不在同一个连通块内,若此时 u 和 v 不同色,那么直接连接即可。若是同色的,那么此时就需要把其中一个连通块内所有点的颜色翻转一下,然后再连接。

对于第二种情况,首先需要把原本这两个连通块的贡献减掉,再加上连接后大连通块的贡献。之后,需要考虑翻转问题。由于直接随意暴力全翻转的话,最差会一直把所有点都翻转一遍,所以复杂度是 O(n^2) 的。

此时就涉及到启发式合并了,即每次暴力翻转较小的连通块内的所有点。观察每个点被翻转的次数,对于当前两个连通块 A 和 B,其中 ,就有 。所以每次合并后,较小的连通块的大小至少翻一倍。所以对于每个点,初始只有自己,大小为 1,每合并一次大小翻一倍,所以其最多就只会被翻转 O(log n) 次,所以是可以的。

七、G - Minimum XOR Walk

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<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 ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
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<int BIT,typename T=ll>
struct Xor_Basis{
    int cnt;
    bool zero;
    bool normal;
    T data[BIT+1];

    Xor_Basis(){reset();}

    void reset(){
        cnt=0;
        zero=false;
        normal=false;
        for(int i=0;i<=BIT;i++)data[i]=0;
    }

    bool insert(T x){
        for(int i=BIT;i>=0&&x;i--){
            if(x>>i&1){
                if(data[i]==0){
                    data[i]=x;
                    cnt++;
                    normal=false;
                    return true;
                }
                x^=data[i];
            }
        }
        zero=true;
        return false;
    }

    void rebuild(){
        if(normal){
            return;
        }

        for(int i=BIT;i>=0;i--){
            if(data[i]==0){
                continue;
            }
            
            for(int j=i-1;j>=0;j--){
                if(data[i]>>j&1){
                    data[i]^=data[j];
                }
            }
        }
        normal=true;
    }

    T query_max(T x=0){
        for(int i=BIT;i>=0;i--){
            if(~(x>>i)&1){
                x^=data[i];
            }
        }
        return x;
    }

    T query_min(T x){
        for(int i=BIT;i>=0;i--){
            if(x>>i&1){
                x^=data[i];
            }
        }
        return x;
    }

    //kth minimum
    T query_kth(ll k){
        rebuild();

        if(zero){
            k--;
        }
        if(k<0||k>=(1ll<<cnt)){
            return -1;
        }

        T res=0;
        for(int i=0;i<=BIT;i++){
            if(data[i]){
                if(k&1){
                    res^=data[i];
                }
                k>>=1;
            }
        }
        return res;
    }

    ll query_rank(T x){
        rebuild();
        
        ll res=0,k=1;
        for(int i=0;i<=BIT;i++){
            if(data[i]){
                if(x>>i&1){
                    res|=k;
                }
                k<<=1;
            }
        }
        return res+zero;
    }

    //线性基合并
    friend constexpr Xor_Basis operator|(const Xor_Basis &lhs, const Xor_Basis &rhs){
        Xor_Basis res=lhs;
        for(int i=0;i<=BIT;i++){
            if(rhs.data[i]){
                res.insert(rhs.data[i]);
            }
        }
        res.zero=lhs.zero|rhs.zero;
        return res;
    }

    constexpr Xor_Basis& operator|=(const Xor_Basis &rhs){
        for(int i=0;i<=BIT;i++){
            if(rhs.data[i]){
                insert(rhs.data[i]);
            }
        }
        return *this;
    }

    //线性基求交
    friend constexpr Xor_Basis operator&(const Xor_Basis &lhs, const Xor_Basis &rhs){
        using i128=__int128_t;

        Xor_Basis<BIT*2,i128>tmp;
        for(int i=0;i<=BIT;i++){
            tmp.insert(((i128)lhs.data[i]<<BIT)|lhs.data[i]);
            tmp.insert((i128)rhs.data[i]<<BIT);
        }
        Xor_Basis res;
        for(int i=0; i<=BIT;i++){
            if(tmp.data[i]){
                res.data[i]=tmp.data[i];
                res.cnt++;
            }
        }
        res.zero=true;
        return res;
    }
};

template<typename T>
struct Trie{
    vector<vector<int>>trie;
    vector<int>pass;
    vector<int>end;
    int cnt;

    int n;
    int kind;
    int base;

    Trie(int _n,int _kind,int _base){
        n=_n;
        kind=_kind;
        base=_base;

        trie.assign(n,vector<int>(kind));
        pass.assign(n,0);
        end.assign(n,0);
        cnt=1;
    }

    void insert(const T &s){
        int cur=1;
        pass[cur]++;
        for(int i=30,path;i>=0;i--){
            path=s>>i&1;
            if(trie[cur][path]==0){
                trie[cur][path]=++cnt;
            }
            cur=trie[cur][path];
            pass[cur]++;
        }
        end[cur]++;
    }

    ll query(int x,int k)
    {
        ll ans=0;
        int cur=1;
        for(int i=30;i>=0;i--)
        {
            int path=x>>i&1;
            if(k>>i&1)
            {
                ans+=pass[trie[cur][path]];
                cur=trie[cur][path^1];
            }
            else
            {
                cur=trie[cur][path];
            }
        }
        return ans+pass[cur];
    }
};

void solve()
{
    int n,m,k;
    cin>>n>>m>>k;
    vector<vector<pii>>g(n+1);
    for(int i=1,u,v,w;i<=m;i++)
    {
        cin>>u>>v>>w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
    }

    Xor_Basis<30,int>base;

    vector<int>path(n+1);
    vector<int>vis(n+1);

    auto dfs=[&](auto &&self,int u,int fa,int cur)->void
    {
        path[u]=cur;
        vis[u]=1;

        for(auto [v,w]:g[u])
        {
            if(v!=fa)
            {
                int nxt=cur^w;
                if(vis[v])
                {
                    base.insert(nxt^path[v]);
                }
                else
                {
                    self(self,v,u,nxt);
                }
            }
        }
    };

    dfs(dfs,1,0,0);

    Trie<int>trie(31*n,2,0);

    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        int cur=base.query_min(path[i]);
        ans+=trie.query(cur,k);
        trie.insert(cur);
    }
    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;
}

首先是一个 Trick:对于图上异或问题,考虑构建生成树,然后将每个环的异或和插入线性基解决。

首先,在对整张图搞出一棵生成树后,从根节点开始递归,构建出从根节点到每个点 u 的异或和 。那么对于从 u 到 v 路径上的异或和,就是 ,因为其 LCA 往上的部分可以异或消掉。

而对于非树边所构成的每个环,其都可以对这条路径的异或和产生影响。因为不管怎么去往每个环,最后回来的时候是可以把这条路上的权值异或消掉的。而每个环都可以选择去或者不去,那么问题就转化成了对于任意点对 (u,v),求 ,其中 s 是所有环的异或和任意组合,能搞出的某个异或和。

那么就有一个 Trick:对于异或任意组合的问题,就可以使用线性基解决。所以在将所有环的异或和插入线性基后,由于暴力枚举点对的复杂度是 O(n^2) 的,所以需要考虑优化。

由于线性基可以满足任意组合,所以考虑将 转化为 。可以证明两者是等价的,因为对于 的结果,因为想要和线性基组合搞出一个最小值,所以对于是 1 的位,肯定是要让其异或上线性基当前位消去的。那么由于这位是 1,就说明这位上 的状态必然不同。所以其单独和线性基组合必然是一个异或上线性基的当前位,一个不异或,所以是不会影响合并结果的,0 的情况类似。

那么在构建出 表示 的最小值后,此时问题就变为了计数有多少对 (i,j) 使得 。这个就可以通过将前缀 插入字典树,每次考察每一位求得。

具体方法是,在将所有前缀 插入字典树后,对于当前数 ,去字典树上从高位到低位考察每一位。保证在遍历的过程中,考虑过的位和 K 相同。那么若当前位在 K 中是 1,此时既可以选 1 也可以选 0。若选择了 0,后续就可以任意选择,所以直接加上当前节点的 pass,然后去选 1 的情况往后考虑。否则,即当前位在 K 中是 0,那么就只能选 0 去后续考虑。最后注意要加上叶节点的 pass, 然后将当前 插入线性基即可。

总结

好不容易能补完一场 abc......

END

相关推荐
im_AMBER2 小时前
Leetcode 151 最大正方形 | 买卖股票的最佳时机 III
数据结构·算法·leetcode·动态规划
Fly Wine2 小时前
Leetcode之简单题:在区间范围内统计奇数数目
算法·leetcode·职场和发展
CoderCodingNo2 小时前
【GESP】C++五级练习题 luogu-P1102 A-B 数对
开发语言·c++·算法
cpp_25012 小时前
B3873 [GESP202309 六级] 小杨买饮料
数据结构·c++·算法·动态规划·题解·洛谷
2301_789015622 小时前
C++11新增特性:可变参数模板、lambda表达式、function包装器、bind绑定、defult和delete
c语言·开发语言·c++·算法·c++11·万能引用
Ahtacca2 小时前
基于决策树算法的动物分类实验:Mac环境复现指南
python·算法·决策树·机器学习·ai·分类
x_xbx2 小时前
LeetCode:567. 字符串的排列
算法·leetcode·职场和发展
沛沛rh452 小时前
力扣 42. 接雨水 - 高效双指针解法(Rust实现)详细题解
算法·leetcode·rust
tankeven2 小时前
HJ158 挡住洪水
c++·算法