线性基学习笔记

线性基

  • 给你一些数,求它们的最大异或和、最小异或和、第k小异或和……
  1. 线性基中任意选择一些数的异或值所构成的集合,等于原序列中任意选择一些数的异或值所构成的集合。(就是说线性基能异或出来的数的最大数量就是原来集合所能异或出来的数的最大数量)
  2. 线性基是满足上述条件的最小集合
  3. 原序列中任何数,都可以由线性基中一些数异或起来得到(由性质1直接得出)
  4. 线性基中不存在一组数,使得它们的异或值为0
  5. 线性基中不存在两组取值集合,使得它们的异或和相等
  6. 总数为 n n n,线性基内有 t o t tot tot个数,则每一个异或数重复 2 n − t o t 2^{n-tot} 2ntot
struct lb
{
    ll d[62];
    lb() { memset(d, 0, sizeof(d)); };

    void operator+=(ll x) // 插入线性基
    {
        for (int i = 61; i >= 0; i--)
            if (x & (1LL << i))
            {
                if (d[i])
                    x ^= d[i];
                else
                {
                    d[i] = x;
                    break;
                }
            }
    }

    ll &operator[](int x) // 重载了下标运算符,所以下面直接用lb[i]来取线性基
    {
        return d[x];
    }

    void operator+=(lb &x) // 暴力合并线性基
    {
        for (int i = 61; i >= 0; i--)
            if (x[i])
                *this += x[i]; // 如果新来的那个线性基这个位置有,就插入
    }

    friend lb operator+(lb &x, lb &y)
    {
        lb z = x;
        for (int i = 61; i >= 0; i--)
            if (y[i])
                z += y;
        return z;
    }

    ll cal() // 计算最大值
    {
        ll ans = 0;
        for (int i = 61; i >= 0; i--)
            if ((ans ^ d[i]) > ans)
                ans ^= d[i];
        return ans;
    }

};

构造方式

  • 从高到低遍历已有线性基,能异或就异或,否则就加入
#define int long long
vector<int> d(65, 0);
void ins(int x)
{
    for (int i = 60; i >= 0; i--)
    {
        if (x & (1LL << i)) // 注意开long long
        {
            if (d[i])
                x ^= d[i];
            else
            {
                d[i] = x;
                break;
            }
        }
    }
}

求max

  • 就是尽量加,能变大就加入,从大到小
ll ans = 0;
for (int i = 60; i >= 0; i--)
{
    if ((ans ^ d[i]) > ans) // 注意运算符优先级
        ans ^= d[i];
}
cout << ans << '\n';

求min

  • 如果存在不能加入线性基的元素,即 t o t < n tot<n tot<n,那么答案为0,否则为最小的非0的 d [ i ] d[i] d[i]

求第k小/大异或和

  • 需要先转化一下线性基数组,对于每一个 d [ i ] d[i] d[i],先对于每一个 j = i − 1 → 0 j = i - 1\to 0 j=i10,把 d [ i ] d[i] d[i]XOR d [ j − 1 ] d[j-1] d[j1],事实上就是把每一个线性基的首位都变成唯一的,每一个首位是所有线性基的该二进制上唯一的1

  • 这样做以后,按照 k k k的二进制值来选择线性基即可,对于非0的 d [ i ] d[i] d[i],k该位为1则异或上这个线性基,否则不异或,但是都要 k / = 2 k/=2 k/=2,这样得到的就是第k小的线性基,求第k大就算一下

  • 注意第一个的判断,因为可能有异或和为0

  • 记线性基中非0的数个数为 t o t tot tot,那么如果 2 t o t < k 2^{tot}<k 2tot<k t o t < n tot < n tot<n,或者 t o t = n 且 2 t o t < = k tot=n 且 2^{tot}<=k tot=n2tot<=k 那么必定不存在这个 k k k

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

vector<ll> d(61);
int tot;
int n;
void ins(ll x)
{
    for (ll i = 60; i >= 0; i--)
        if (x & (1LL << i))
            if (d[i])
                x ^= d[i];
            else
            {
                tot++;
                d[i] = x;
                break;
            }
}

void change()
{
    for (int i = 1; i <= 60; i++)
        for (int j = 0; j < i; j++)
            if (d[i] & (1LL << j))
                d[i] ^= d[j];
}

ll k_th(ll k)
{
    ll ans = 0;
    if (k == 1 && tot < n)//!!!!!!
        return 0;
    if ((k > (1LL << tot) && tot < n) || (k >= (1LL << tot) && tot == n)) //!!!!!!
        return -1;
    if (tot < n) // 将异或和为0去掉
        k--;
    for (int i = 0; i <= 60; i++)
    {
        if (d[i])
        {
            if (k % 2 == 1)
                ans ^= d[i];
            k >>= 1;
        }
    }
    return ans;
}

void solve()
{
    cin >> n;
    ll tmp;
    for (int i = 1; i <= n; i++)
    {
        cin >> tmp;
        ins(tmp);
    }
    change();
    int q;
    cin >> q;
    while (q--)
    {
        ll k;
        cin >> k;
        cout << k_th(k) << '\n';
    }
}

signed main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    cout << endl;
    system("pause");
    return 0;
}

树上倍增+线性基!!

  • 题意:给你一棵树,有q次询问,每次询问 l , r l, r l,r回答 l , r l,r l,r两点之间最短路径可以获得的最大异或和
  • 求最大异或和,考虑线性基,利用线性基的可并性,合并就是一个线性基暴力插入另一个线性基即可 O ( l o g 2 n ) O(log^2n) O(log2n)
  • 而树上两点最短路径,容易想到 L C A LCA LCA,我们可以在求 l c a lca lca的同时处理一下倍增线性基,查询时与向上找 l c a lca lca同步合并线性基即可

L B [ x ] [ i ] = L B [ x ] [ i − 1 ] + L B [ f a [ x ] [ i − 1 ] ] [ i − 1 ] LB[x][i]=LB[x][i-1]+LB[fa[x][i-1]][i-1] LB[x][i]=LB[x][i1]+LB[fa[x][i1]][i1]

  • 注意向上跳的时候,先合并线性基,再跳
  • 找到 l c a lca lca后,答案就是合并出来的线性基可以异或出来的最大值
  • O ( n l o g 3 n ) O(nlog^3n) O(nlog3n)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

vector<ll> a;
vector<vector<int>> e; // 记得定义全局边

struct lb
{
    ll d[62];
    lb() { memset(d, 0, sizeof(d)); };

    void operator+=(ll x) // 插入线性基
    {
        for (int i = 61; i >= 0; i--)
            if (x & (1LL << i))
            {
                if (d[i])
                    x ^= d[i];
                else
                {
                    d[i] = x;
                    break;
                }
            }
    }

    ll &operator[](int x) // 重载了下标运算符,所以下面直接用lb[i]来取线性基
    {
        return d[x];
    }

    void operator+=(lb &x) // 暴力合并线性基
    {
        for (int i = 61; i >= 0; i--)
            if (x[i])
                *this += x[i]; // 如果新来的那个线性基这个位置有,就插入
    }

    friend lb operator+(lb &x, lb &y)
    {
        lb z = x;
        for (int i = 61; i >= 0; i--)
            if (y[i])
                z += y;
        return z;
    }

    ll cal() // 计算最大值
    {
        ll ans = 0;
        for (int i = 61; i >= 0; i--)
            if ((ans ^ d[i]) > ans)
                ans ^= d[i];
        return ans;
    }

} LB[20010][16]; // 表示第i个点到其第2^j 的祖先的线性基

class LCA
{
private:
    vector<vector<int>> fa;
    vector<int> dep;
    int n;

public:
    LCA() {}
    LCA(int _n)
    {
        n = _n;
        fa.resize(n + 1, vector<int>(17));
        dep.resize(n + 1);
    }

    void dfs(int x, int f)
    {
        dep[x] = dep[f] + 1;
        fa[x][0] = f;
        LB[x][0] += a[x]; // 自身插入线性基
        // for (int i = 1; (1 << i) <= dep[x]; i++) // 写里面会T
        // {
        //     fa[x][i] = fa[fa[x][i - 1]][i - 1];
        //     LB[x][i] = LB[x][i - 1] + LB[fa[x][i - 1]][i - 1]; // 维护倍增线性基
        // } // 这是显然的,除了加上父亲的上面那一段线性基,还要加上自己到父亲的那个线性基
        for (auto y : e[x])
            if (y != f)
                dfs(y, x);
    }

    void get()
    {
        for (int i = 1; i <= 15; i++)
            for (int x = 1; x <= n; x++)
            {
                fa[x][i] = fa[fa[x][i - 1]][i - 1];
                LB[x][i] = LB[x][i - 1] + LB[fa[x][i - 1]][i - 1];
            }
    }

    ll query(int u, int v)
    {
        if (dep[u] < dep[v]) // 定u的深度更大
            swap(u, v);
        int temp = dep[u] - dep[v]; // 深度差,跳到相同深度

        lb z;
        for (int i = 0; (1LL << i) <= temp; i++)
            if ((1LL << i) & temp) // 这里就相当于把路径拆分成很多个2的次方相加
            {
                z += LB[u][i]; // 合并线性基 注意顺序!!
                u = fa[u][i];
            }

        if (u == v)
        {
            z += a[u];
            return z.cal();
        }
        // 同深度了,开始同步跳,跳到它们父亲相同的点上,
        // 因为如果是以它们自己相同判断,跳超过了也不知道
        for (int i = (int)(log(n) / log(2)); i >= 0; i--) // log(n)/log(2)是n里最大的2次方
        {
            if (fa[u][i] != fa[v][i]) // 说明不够,没到lca,则跳
            {
                z += LB[u][i];
                z += LB[v][i]; // 注意先加线性基再跳!!!
                u = fa[u][i];
                v = fa[v][i];
            }
        }
        z += LB[u][0];
        z += LB[v][0];
        z += a[fa[u][0]];
        return z.cal();
    }
};

void solve()
{
    int n, q;
    cin >> n >> q;
    vector<ll>(n + 1, 0).swap(a);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    e.resize(n + 1);
    for (int i = 1; i < n; i++)
    {
        int a, b;
        cin >> a >> b;
        e[a].push_back(b);
        e[b].push_back(a);
    }

    LCA lca = LCA(n);
    lca.dfs(1, 0);
    lca.get();
    while (q--)
    {
        int x, y;
        cin >> x >> y;
        cout << lca.query(x, y) << '\n';
    }
}

signed main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    cout << endl;
    system("pause");
    return 0;
}

线性基+贪心+排序

  • 比较简单,一般根据题意,排序求一种权值最大或最小的线性基

最大不同异或值的数量

  • 由:线性基中任意个数的异或值所构成的集合,等于原序列中任意个数的异或值所构成的集合
  • 对集合建立线性基,非0数量记为 t o t tot tot,则答案就是 2 t o t 2^{tot} 2tot

给出一个值反求其为第几大,异或数个数

  • 总数为 n n n,线性基内有 t o t tot tot个数,则每一个异或数重复 2 n − t o t 2^{n-tot} 2ntot

  • 上面这些其实就是证明从外面的 n − k n -k nk个数选任意个插入线性基,总可以通过一些操作使线性基的一个异或和保持不变,保持 2 n − k 2^{n-k} 2nk

  • 对于求一个值为第几大,根据前面求第k大的方法,我们可以二分得到

  • 注意:用之前方法得到的第k大是从1开始算的

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll d[40];
bool ins(ll x)
{
    for (int i = 34; ~i; i--)
        if (x & (1LL << i))
            if (d[i])
                x ^= d[i];
            else
            {
                d[i] = x;
                return true;
            }
    return false;
}

const ll mod = 10086;
ll qsm(ll a, ll b)
{
    ll ans = 1;
    while (b)
    {
        if (b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

void solve()
{
    int n;
    cin >> n;
    vector<ll> a(n + 1);
    ll tot = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        if (ins(a[i]))
            tot++;
    }
    ll q;
    cin >> q;
    if (!q)
    {
        cout << 1 << '\n';
        return;
    }

    for (int i = 1; i <= 34; i++)
        for (int j = 0; j < i; j++)
            if (d[i] & (1LL << j))
                d[i] ^= d[j];

    auto check = [&](ll x) -> bool
    {
        ll ans = 0;
        for (int i = 0; i <= 34; i++)
        {
            if (d[i])
            {
                if (x % 2 == 1)
                {
                    ans ^= d[i];
                }
                x >>= 1;
            }
        }
        if (ans >= q)
            return true;
        return false;
    };

    // 这里算的是从1开始的排位,由于还有0,所以需要的那个数在下一个
    ll l = 0, r = (1LL << tot) - 1, ans = 0;
    while (l <= r)
    {
        ll mid = (l + r) >> 1;
        if (check(mid))
        {
            ans = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    ll cnt = (qsm(2, n - tot)) * (ll)(ans);
    cout << (cnt + 1) % 10086 << '\n';
}
// 0 1 2 3 4 5
signed main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    cout << endl;
    system("pause");
    return 0;
}

异或数个数,离线处理转化

  • 题意:给定一个序列a,有q次询问,每次给出 L 和 r L和r Lr,回答 [ 1 , L ] [1,L] [1,L]序列中异或和为 x x x的不同集合个数
  • 由于每次是询问前缀和,可以考虑强行离线,根据询问的 L L L,从小到达进行计算,这样只需要处理一个线性基
  • 对于每一个 x x x,如果它能够插入线性基,则它不可以被表示,答案为 0 0 0,否则答案为 2 n − t o t 2^{n-tot} 2ntot
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll mod = 1e9 + 7;
ll qsm(ll a, ll b)
{
    ll ans = 1;
    while (b)
    {
        if (b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

struct LineBasis
{
    ll d[25];
    ll tot, n;
    LineBasis()
    {
        memset(d, 0, sizeof(d));
        n = 0;
        tot = 0;
    };

    void operator+=(ll x)
    {
        n++;
        for (int i = 21; ~i; i--)
            if (x & (1LL << i))
                if (d[i])
                    x ^= d[i];
                else
                {
                    tot++;
                    d[i] = x;
                    break;
                }
    }

    int check(ll x)
    {
        for (int i = 21; ~i; i--)
            if (x & (1LL << i))
                if (d[i])
                    x ^= d[i];
                else
                {
                    return 0;
                }
        return 1;
    }

    ll &operator[](int x)
    {
        return d[x];
    }

    void operator+=(LineBasis &x)
    {
        for (int i = 21; ~i; i--)
            if (x[i])
                *this += x[i];
    }

    ll query()
    {
        return qsm(2, n - tot);
    }
} lb;

void solve()
{
    ll n, q;
    cin >> n >> q;
    vector<ll> a(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    vector<vector<pair<int, ll>>> ask(n + 1);
    for (int i = 1; i <= q; i++)
    {
        int x;
        ll y;
        cin >> x >> y;
        ask[x].push_back({i, y});
    }
    // 离线处理,转为顺序的
    vector<ll> ans(q + 1);
    for (int i = 1; i <= n; i++)
    {
        lb += a[i];
        for (auto [x, y] : ask[i])
        {
            ans[x] = lb.query() * lb.check(y); //
        }
    }
    for (int i = 1; i <= q; i++)
        cout << ans[i] << '\n';
}

signed main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    cout << endl;
    system("pause");
    return 0;
}

完全平方数trick+二进制转化

  • 题意:给定一个序列a,其中每个数 1 ≤ a i ≤ 70 1\le a_i \le 70 1ai70,问这个序列中有多少个非空子集,能够使得子集内所有数的乘积为一个完全平方数
  • 一个数使完全平方数,当且仅当它的乘数里面的每个质数都出现偶数个!!!
  • 注意到 a i a_i ai的值很小,我们考虑将每一个 a i a_i ai,转化为每个质数出现了奇数次还是偶数次的 b i t m a s k bitmask bitmask
  • 转化后,两个数相乘,就相当于两个 b i t m a s k bitmask bitmask的异或,而要满足题目条件,就是要求有多少组数异或和为0因为这样才是完全平方数
  • 这样的话直接放入线性基中, 2 n − t o t − 1 2^{n-tot}-1 2ntot1即为答案(因为这里不能是空集)
  • trick: 如果有一些奇怪的限制条件,如每行每列只能取奇数/偶数个,可以在插入的数前面加位,表示它所属哪一行/列
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int prime[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67};
int d[50];
bool ins(int x)
{
    for (int i = 19; ~i; i--)
        if (x & (1LL << i))
            if (d[i])
                x ^= d[i];
            else
            {
                d[i] = x;
                return true;
            }
    return false;
}

ll mod = 1e9 + 7;

ll qsm(ll a, ll b)
{
    ll ans = 1;
    while (b)
    {
        if (b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

void solve()
{
    int n;
    cin >> n;
    ll cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        ll bit = 0;
        for (int j = 0; j < 19; j++) // 转化为质数个数奇偶性的bitmask
        {                            // 每一位对应一个质数
            if (x % prime[j])
                continue;
            ll z = 0;
            while (x % prime[j] == 0)
            {
                x /= prime[j];
                z ^= 1;
            }
            bit |= (z << j);
        }
        if (ins(bit))
            cnt++;
    }
    cout << qsm(2, n - cnt) - 1 << '\n';
}

signed main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    //    cin >> t;
    while (t--)
        solve();
    cout << endl;
    system("pause");
    return 0;
}

线性基在图论中的应用,图上最大异或路径❗

  • 题意:给你一个无向图,每条边有权,每条边可以重复走,求1到n的最大异或和路径
  • 显然不可能枚举所有路径,可以考虑拆分路径,拆成一堆圈和一条链
  • 假设选择了一条路径,可能并非最优,走这条路径时,我们可以通过一些边走向圈,然后走一遍圈再通过原来的路径再回到本来的路径上,这样一顿操作,只有那个圈会造成贡献,来回的路径会被抵消
  • 基于这种思想,我们就是想选择一些圈的异或和(圈必须一整个),来构造出原本路径上本来得不到的更大的异或和
  • 那么,就想到将这些圈都塞入一个线性基里,求最大异或和就容易了
  • 但是,我们一开始选的并不是最优路径呢?其实任意选择即可
  • 证明:假设到达n有两条路径 A , B A,B A,B,我们选择了A,而B却更优,但是这两条路径必定构成了一个圈,我们求最大异或的时候,如果B更优,求异或和的时候肯定会将这个大圈异或上,那么就会得到B,故选任何一条路径都可以
  • 统计环上异或和实现也很简单,只需要每次入一个点的时候都记录该点之前得到的异或和,再次搜到这个点时,再异或上原本那个点异或和,那么就可以得到环的异或和
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll d[68];

void ins(ll x)
{
    for (int i = 62; ~i; i--)
        if (x & (1LL << i))
            if (d[i])
                x ^= d[i];
            else
            {
                d[i] = x;
                break;
            }
}

ll query(ll x)
{
    ll res = x;
    for (int i = 62; ~i; i--)
    {
        if ((res ^ d[i]) > res)
            res ^= d[i];
    }
    return res;
}

void solve()
{
    int n, m;
    cin >> n >> m;
    struct node
    {
        int v;
        ll w;
    };
    vector<vector<node>> e(n + 1);
    for (int i = 1; i <= m; i++)
    {
        int a, b;
        ll c;
        cin >> a >> b >> c;
        e[a].push_back({b, c});
        e[b].push_back({a, c});
    }

    vector<int> vis(n + 1, 0);
    vector<ll> del(n + 1); // 记录到达该点的总异或值,用于计算圈上的异或值
    // 先把所有的环作为线性基
    auto dfs = [&](auto dfs, int x, ll res) -> void
    {
        del[x] = res, vis[x] = 1;
        for (auto [y, w] : e[x])
        {
            if (!vis[y])
                dfs(dfs, y, res ^ w);
            else
                ins(res ^ del[y] ^ w); // 线性基插入圈
        }
    };

    dfs(dfs, 1, 0);
    cout << query(del[n]) << '\n';
}

signed main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    //    cin >> t;
    while (t--)
        solve();
    cout << endl;
    system("pause");
    return 0;
}

图论+可删除线性基+离线处理

  • 题意:有一个无向图,求从1到n的最大异或和路径,与上面那个不同的地方在:本题还有特殊的道路,在之后的q次操作中,可以新建边,更改边,删除边,求每时每刻的从1到n的最大异或和路径**(本题需要用 b i t s e t bitset bitset处理)**
  • 由上面那道题我们知道,就是求所有环的最大异或和即可,对于线性基可能被删除这种情况,考虑可删除线性基(非在线)对于线性基中的每个元素,我们再记录一个它被删除的时间,然后对于每一位,我们尽量用删除时间更晚的,这样肯定更优,具体操作:
void ins(int time, bitset<1010> x) // 插入一个删除时间为time的x元素
{
    for (int i = 1000; i >= 0; i--)
    {
        if (!x[i])
            continue;
        if (tms[i] < time)// 如果新的时间更长,则交换,这里实际完成了插入操作
        {
            swap(x, d[i]);
            swap(tms[i], time);
        }
        x ^= d[i];
    }
}
  • 对于查询最大值,相应的也需要根据时间判断是否可取
void query(int time)
{
    bitset<1010> res;
    for (int i = 1000; i >= 0; i--)
        if (tms[i] > time && !res[i])
            res ^= d[i];
    print(res);
}
  • 离线处理操作还是老一套,加一条边实际上也是加了一个环,直接试图插入线性基即可。由于存在更新操作,直接当作原本的边被删除了,加入了一个新边,此处需要给每条边重新编号进行处理,有点乱
  • 记录下所有操作后,按加入边的顺序一个一个插入,一个一个回答当前最大异或和即可
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int inf = 1e7;
bitset<1010> d[1010];
int tms[1010]; // 记录d中每一个元素的删除

void ins(int time, bitset<1010> x) // 插入一个删除时间为time的x元素
{
    for (int i = 1000; i >= 0; i--)
    {
        if (!x[i])
            continue;
        if (tms[i] < time)
        {
            swap(x, d[i]);
            swap(tms[i], time);
        }
        x ^= d[i];
    }
}

bitset<1010> myread()
{
    string s;
    cin >> s;
    bitset<1010> w(s);
    return w;
}

void print(bitset<1010> &x)
{
    bool start = false;
    for (int i = 1000; i >= 0; i--)
    {
        if (x[i] || start)
            cout << (x[i] ? 1 : 0);
        if (x[i])
            start = true; // 第一个1之后才开始输出0
    }
    if (!start)
        cout << 0;
    cout << '\n';
}

void query(int time)
{
    bitset<1010> res;
    for (int i = 1000; i >= 0; i--)
        if (tms[i] > time && !res[i])
            res ^= d[i];
    print(res);
}

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

    struct node
    {
        int v;
        bitset<1010> w;
    };

    vector<vector<node>> e(1010);
    vector<ll> num(1010);
    for (int i = 1; i <= m; i++)
    {
        int a, b;
        cin >> a >> b;
        auto w = myread();
        e[a].push_back({b, w});
        e[b].push_back({a, w});
    }

    vector<int> vis(1010, 0);
    vector<bitset<1010>> dis(1010);

    auto dfs = [&](auto dfs, int x, bitset<1010> res) -> void
    {
        dis[x] = res, vis[x] = 1;
        for (auto [y, w] : e[x])
        {
            if (!vis[y])
                dfs(dfs, y, res ^ w);
            else
                ins(inf, (res ^ dis[y] ^ w)); // 加入圈
        }
    };

    bitset<1010> tmp(0);
    dfs(dfs, 1, tmp);
    query(0);

    vector<bitset<1010>> val(1010);
    vector<int> del(1010, inf); // del[i] = j 表示编号为i的铁路删除时间为j
    vector<int> add(1010);      // add[i] = j 表示i时间加入了一个编号为j的边
    vector<int> edg(1010);      // 记录边的编号,(给边重新编号)
    vector<pair<int, int>> road(1010);
    int tot = q + 1;
    int cnt = 0;
    for (int i = 1; i <= q; i++)
    {
        string s;
        cin >> s;
        if (s[1] == 'd')
        {
            int a, b;
            cin >> a >> b;
            auto s = myread();
            add[i] = ++cnt;
            edg[cnt] = cnt; // 给边标号
            val[cnt] = (s ^ dis[a] ^ dis[b]);
            road[cnt] = {a, b};
        }
        else if (s[1] == 'h')
        {
            int k;
            cin >> k;
            auto s = myread();
            del[edg[k]] = i;
            add[i] = --tot;
            edg[k] = tot;
            val[tot] = (s ^ dis[road[k].first] ^ dis[road[k].second]);
            // 修改
        }
        else // 应该是记录删除的时间之后,再进行插入
        {
            int k;
            cin >> k;
            del[edg[k]] = i; // 边在这个时间删除
        }
    }

    for (int i = 1; i <= q; i++)
    {
        if (add[i])
            ins(del[add[i]], val[add[i]]);
        query(i);
    }
}

signed main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    cout << endl;
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值