K - City
题意:n城市之间连接无方向的道路,每个道路都有能量,敌人发动攻击,来摧毁这些道路,如果敌人发动x的攻击,则所有能力小于等于x的道路都将被摧毁,问有有多少对城市可以到达对方
思路:这题一开始看到连通性就想到了并查集,但是并查集只能加边啊,并不能减边,不但知道该怎么想了,后来听了同学的想法,非常nice,他问的不是连通块中的元素个数,而是对数,如果一个连通块得大小为你n,那么对答案贡献就为,就是n*(n-1)/2,那么我们知道了连通块的大小也就知道了答案,还有就是如果每次询问都重新找连通块时间复杂度比较高并且并查集不支持删边操作,可以离线操作,把询问排序,把删边改成加边,答案直接在上一次的基础上更新。类似于最小生成树的方式慢慢增加路径。
我们知道将边权和攻击从大到小排序后,我们进行询问由于我们从将攻击力降序,那么现在还存在的边就很少了,而继续下去攻击力变小,边的存在就会越来越多,故转变为加边,操作每次的答案都在上一次更新
#include <bits/stdc++.h>
using namespace std;
#define pi acos(-1)
#define X first
#define Y second
#define endl "\n"
#define int long long
#define pb push_back
typedef pair<int, int> PII;
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define Ysanqian ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
const int N = 1e6 + 10, M = 1 << 10, inf = 0x3f3f3f3f, mod = 998244353;
int n, m, q;
int p[N];
int siz[N];
int ans[N];
struct node // 所有边由大到小排序
{
    int x, y, w;
    bool operator<(const node &W) const
    {
        return w > W.w;
    }
} rode[N];
struct query // 询问操作从大到小排序
{
    int id, val;
    bool operator<(const query &VAL) const
    {
        return val > VAL.val;
    }
} query[N];
int find(int x)
{
    if (x != p[x])
        p[x] = find(p[x]);
    return p[x];
}
void solve()
{
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++)
    {
        p[i] = i;
        siz[i] = 1;
    }
    for (int i = 0; i < m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        rode[i] = {u, v, w};
    }
    for (int i = 0; i < q; i++)
    {
        int x;
        cin >> x;
        query[i] = {i, x};
    }
    int j = 0;
    int sum = 0; // 由于伤害越高,联通的越少,
    // 故sum有种递推的关系
    // 对于那些伤害小的就不需要再重复去算前面得那些了
    sort(rode, rode + m);
    sort(query, query + q);
    for (int i = 0; i < q; i++)
    {
        int val = query[i].val;
        while (rode[j].w >= val && j < m) // 如果大的伤害都没有将其毁坏,那么小的伤害一定不肯能
        {
            int x = rode[j].x, y = rode[j].y;
            int a = find(x), b = find(y);
            if (a != b)
            {
                sum += siz[a] * siz[b];
                p[a] = b;
                siz[b] += siz[a];
            }
            j++;
        }
        ans[query[i].id] = sum;
    }
    for (int i = 0; i < q; i++)
        cout << ans[i] << endl;
}
signed main()
{
    Ysanqian;
    int T;
    // T=1;
    cin >> T;
    while (T--)
        solve();
}
D - Lowbit
题意:给你n个数,m次操作。
 操作1:给[ l , r ]之间的数加上一个自身的lowbit(二进制下的最低位)(不明白可以去百度一下)
 操作2:查询l,r区间和。
思路:很明显这是一个线段树,但是我们需要维护些什么呢,一就是区间和,再就是根据这个lowbit的性质,如果一个数为偶数,那么它的lowbit就是他本身,奇数就是将二进制最右边的一向左移动一位,那么如果一个区间全部为2的倍数,他进行lowbit运算就是变为的二倍,反之据题目中a[i]的范围可以知道一个数最多只能进行15次第二种情况的操作一定可以变成偶数,故我们对于非偶数就单点修改,由此得到了有一个需要维护的东西那就是否区间全部为二的倍数的标记
#include <bits/stdc++.h>
using namespace std;
#define pi acos(-1)
#define X first
#define Y second
#define endl "\n"
#define int long long
#define pb push_back
typedef pair<int, int> PII;
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define Ysanqian ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
const int N = 2e5 + 10, M = 1010, inf = 0x3f3f3f3f, mod = 998244353;
int w[N], n, m;
int qpow(int a, int b, int mod)
{
    int res = 1;
    while (b)
    {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
int lowbit(int x) // 返回二进制对后一个1为几,我们发现,对于二的倍数,返回原值,否则会使其二进制最后一个1向左移动一位
{
    return x & -x;
}
struct node
{
    int l, r, sum, lazy;
    bool bit;
} tr[N << 2];
void change(int u, int k)
{
    int mul = qpow(2, k, mod);
    tr[u].sum = (tr[u].sum * mul) % mod;
    tr[u].lazy = (tr[u].lazy + k) % mod;
}
void pushdown(int u) // 当用到下区间的时候,下区间依靠懒标记来更新数据,也就是一个区间和
{                    //由于在不是二的倍数的时候我们单点修改并进行pushup,所以只有都为二的
    if (tr[u].lazy)   //倍数时pushdown才有用,故不会影响bit标记
    {
        change(u << 1, tr[u].lazy);
        change(u << 1 | 1, tr[u].lazy);
        tr[u].lazy = 0;
    }
}
void pushup(node &u, node &l, node &r)//修改之后,对于上去间的影响就是区间和,和bit标记
{
    u.sum = ((l.sum + r.sum) % mod + mod) % mod;
    u.bit = l.bit && r.bit;
}
void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r)
{
    tr[u].l = l, tr[u].r = r;
    tr[u].bit = 0, tr[u].lazy = 0, tr[u].sum = 0; // 不是不初始化就为0吗,找了3个小时。。。。。可恶
    if (l == r)
    {
        tr[u].sum = w[l];
        if (tr[u].sum == lowbit(tr[u].sum)) // 如果该数为二的倍数
            tr[u].bit = 1;
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    pushup(u);
}
void modify(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r && tr[u].bit)
    {
        change(u, 1); // 是二的倍数加上本身,就是扩大二倍即可
        return;
    }
    if (tr[u].l == tr[u].r) // 如果不是二的倍数我们直接单点修改即可
    {
        tr[u].sum += lowbit(tr[u].sum);
        if (tr[u].sum == lowbit(tr[u].sum))
            tr[u].bit = true;
        return;
    }
    pushdown(u); // 既然区间修改了记得pushdown
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid)
        modify(u << 1, l, r);
    if (r > mid)
        modify(u << 1 | 1, l, r);
    pushup(u);
}
node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u];
    pushdown(u); // 起作用就是我们打的懒标记并未进行计算,但我们还要继续向下遍历寻找,
                 // 那我们这时才用懒标记跟新区间,就是只有被询问倒是才跟新,节省时间吗
    int mid = tr[u].l + tr[u].r >> 1;
    if (r <= mid) // 在左
        return query(u << 1, l, r);
    else if (l > mid) // 在右
        return query(u << 1 | 1, l, r);
    else // 在中间
    {
        node res;
        auto left = query(u << 1, l, r), right = query(u << 1 | 1, l, r);
        pushup(res, left, right);
        return res;
    }
}
void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> w[i];
    build(1, 1, n);
    cin >> m;
    for (int i = 1; i <= m; i++)
    {
        int op, l, r;
        cin >> op >> l >> r;
        if (op == 1)
            modify(1, l, r);
        else if (op == 2)
            cout << (int)(query(1, l, r).sum % mod + mod) % mod << endl;
    }
}
signed main()
{
    Ysanqian;
    int T;
    // T = 1;
    cin >> T;
    while (T--)
        solve();
    return 0;
}A - Matrix
题意::给你一个n * n 的矩阵填充了[1 , ] 的数,每一行都会贡献一个最小值ai,S = {a1,a2,…,an} ∩ {1,2,…,n} 求ΣS
一行的最小值是1~n中的数时,才对答案有贡献。
大致题意:给你一个n * n 的矩阵填充了[1 , n2] 的数,每一行都会贡献一个最小值ai,S = {a1,a2,…,an} ∩ {1,2,…,n} 求ΣS
一行的最小值是1~n中的数时,才对答案有贡献。
 首先从1~n枚举一行的最小值 记为i
 这一行剩余n-1个数都要比i大,所以有C(n * n - i ,n-1)种选法
 然后把这一行的n个数全排列 n!
 剩余的n * n - n个数填到剩下的n-1行中,随意排列 共(n * n - n)!种排法
 把之前带有i的那一行插进去,组成n行 共n种方法
然后这个排法保证最小值 i 对答案有贡献 即+1
 所以有多少排法 答案就加多少
对于每一个 i ,排法有: C(n * n - i ,n-1)* n! * (n * n - n)!* n
#include <bits/stdc++.h>
using namespace std;
#define pi acos(-1)
#define lowbit(x) x & (-x)
#define X first
#define Y second
#define endl "\n"
#define int long long
#define pb push_back
typedef pair<int, int> PII;
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define Ysanqian ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
const int N = 25000010, M = 1010, inf = 0x3f3f3f3f, mod = 998244353;
int f[N];
int n;
int qpow(int a, int b, int mod)
{
    int res = 1;
    while(b)
    {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
int C(int n, int m)
{
    int ans = 1;
    ans = ans * f[n] % mod;
    ans = ans * qpow(f[m], mod - 2, mod)%mod;
    ans = ans * qpow(f[n - m], mod - 2, mod)%mod;
    return ans;
}
void init()
{
    f[0] = 1;
    f[1] = 1;
    for (int i = 2; i <= N; i++)
        f[i] = f[i - 1] * i % mod;
}
void solve()
{
    int ans = 0;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int  now = C(n * n - i, n - 1) * f[n] % mod * f[n * n - n] % mod * n % mod; 
        ans = (ans + now) % mod;
    }
    cout<<(int)ans<<endl;
}
signed main()
{
    Ysanqian;
    int T;
    init();
    // T = 1;
    cin >> T;
    while (T--)
        solve();
    return 0;
}


















