2024牛客暑期多校训练营2(B,C,E,H)

B

题目大意

给一张点数为n,边数为m的图,多次询问k个点的子图,求其最小生成树权值

题解

可以发现,对求子图影响最大的就是子图的边数,如果采用朴素的暴力,考虑到存在一个点,其连接的边数比 1 e 5 1e5 1e5少一点,如果多次询问这个点,再加上任意点来保证询问不相同,会导致其时间复杂度近似 O ( q 1 e 5 l o g 1 e 5 ) O(q1e5log{1e5}) O(q1e5log1e5),其中 q q q可以接近于 1 e 5 1e5 1e5,这个时间复杂度不可以接受。

考虑分治,可以发现,点的个数和询问次数是息息相关的,而对于一张完全图,其边的个数最多为 n ∗ ( n − 1 ) n*(n-1) n(n1),其中 n n n是这张图的点数,那么如果存在一个点连接了特别多的边,我们可以考虑删除其无用的边

不妨采用 s q r t ( n ) sqrt(n) sqrt(n)作为点分界线,那么对于小于 s q r t ( n ) sqrt(n) sqrt(n)的询问,我们可以暴力地求出其每对点之间是否存在边,但需要注意的是,不可以直接遍历邻接表,这样时间复杂度仍是爆炸,应采用其他方法,我是采用了二分来查找两点之间是否存在边,在这一步骤上应该不是正解,此时时间复杂度为 O ( k 2 ∗ ( ? l o g 1 e 5 ) + k ∗ l o g k 2 ) O(k^2*(?log1e5) + k*logk^2) O(k2(?log1e5)+klogk2)

对于大于 s q r t ( n ) sqrt(n) sqrt(n)的询问,此时可以发现,其最大的询问应该为 s q r t ( n ) sqrt(n) sqrt(n)次,此时我们可以直接暴力地遍历邻接表去查找询问的点之间连接的边,此时就算存在一个特别大的边数,时间复杂度也不会爆炸,此时时间复杂度极限为 O ( s q r t ( n ) 1 e 5 l o g 1 e 5 ) O(sqrt(n)1e5log1e5) O(sqrt(n)1e5log1e5)

可以发现,在上面的时间复杂度还是比较大的,基本有1e9,虽然比较难接近这个极限。

代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 1e18;
const int mod = 998244353;
const int N = 1e5+5;

struct node{
    int x,y,z;
    node (int _x,int _y,int _z){
        x=_x;y=_y;z=_z;
    }
    bool operator<(const node m)const{
        return y<m.y;
    }
};

vector<vector<node>> G(N);
vector<vector<int>> H(N);
int fa[N];
int n,m;

bool cmp(node a,node b){
    return a.z<b.z;
}

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

int kruskal(vector<int> nodes,vector<bool> is,vector<node> edges){
    int sum=0;
    for(auto i : edges){
        if(!is[i.x] || !is[i.y]) continue;
        int x = find(i.x);
		int y = find(i.y);
		if(x == y) continue;
		fa[y] = x;
		sum += i.z;
    }
    int ans=0;
    for(auto i : nodes){
        if(i== fa[i]){
            ans++;
        }
    }
    if(ans==2) return sum;
    else return -1;
}

void solve(){
    int q;
    cin >> n >> m >> q;
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin >> x >> y >> z;
        G[x].push_back(node(x,y,z));
        G[y].push_back(node(y,x,z));
        
    }
    for(int i = 1;i<=n;i++){
        sort(G[i].begin(),G[i].end());
    }
    for(int i=1;i<=q;i++){
        int k;
        cin >> k;
        vector<int> a(k+1);
        vector<node> edges;
        vector<bool> isAppear(n+1,false);
        for(int j=1;j<=k;j++){
            cin >> a[j];
            isAppear[a[j]]=true;
            fa[a[j]]=a[j];
        }
        if (k < sqrt(n)){
            for (int j = 1;j<=k;j++){
                for(int o = j + 1 ;o<=k;o++) {
                    int tt = upper_bound(G[a[j]].begin(),G[a[j]].end(),node(a[j],a[o],0)) - G[a[j]].begin();
                    if (tt-1 >= 0 && G[a[j]][tt-1].y == a[o]) {
                        edges.push_back(G[a[j]][tt-1]);
                    }
                }
            }
        }else{
            for (int j = 1;j<=k;j++){
                for(auto t : G[a[j]]){
                    if (isAppear[t.y]){
                        edges.push_back(t);
                    }
                }
            }
        }
        
        sort(edges.begin(),edges.end(),cmp);
        int ans = kruskal(a,isAppear,edges);
        cout << ans << '\n';
        
    }
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    t=1;
    while(t--){
        solve();
    }
}
/*
5 10 4
1 2 1
1 3 1
1 4 1
1 5 1
2 3 2
2 4 2
2 5 2
3 4 3
3 5 3
4 5 4
4 2 3 4 5
3 3 4 5
2 4 5
1 5
*/

C

题目大意

有一个大小为 2 ∗ n 2 * n 2n的矩阵,由 R R R W W W组成,可以选择任意R作为起点,可以上下左右移动,不可移动到 W W W,移动后原位置变为 W W W不可移动,询问最多可以移动的次数。

题解

直接递推就行,每个位置可以移动的值都由前面的位置转移得到,然后取最大值即可,可以直接看代码

代码
n = int(input())
a = ['W' + input(), 'W' + input()]
b = [[0] * (n + 1), [0] * (n + 1)]

ans = 0
for i in range(1, n + 1):
    if a[0][i] == 'R':
        b[0][i] = b[0][i - 1] + 1
    if a[1][i] == 'R':
        b[1][i] = b[1][i - 1] + 1
    if a[0][i] == a[1][i] == 'R':
        b[0][i], b[1][i] = max(b[0][i], b[1][i] + 1), max(b[1][i], b[0][i] + 1)
    ans = max(ans, b[0][i], b[1][i])
ans = max(0, ans-1)
print(ans)

E

题目大意

找到一个严格小于 x x x的整数 y y y,使得 g c d ( x , y ) = x ⨁ y gcd(x,y) = x \bigoplus y gcd(x,y)=xy

题解

一般来说,应该先打表,然后就没有然后了

考虑异或的定义,显然,异或后的值是必须要小于 x x x的(来自 g c d gcd gcd),可以发现,如果去凑一个y值满足其是 x x x的一个因子,这非常艰难,但显然这答案是很多的,考虑异或的性质 x ⨁ y = t ⇔ x ⨁ t = y x \bigoplus y = t \Leftrightarrow x \bigoplus t = y xy=txt=y,此时我们可以构造一个 x x x的因子 t t t,使其满足 t ⨁ x = y t \bigoplus x = y tx=y,那么就找到了所要求得的 y y y,此时应取 x x x的最小因子,也就是 x x x的二进制最后一个1之后的部分,判断合法即可。

代码
import math

t = int(input())
for _ in range(t):
    x = int(input())
    a = bin(x)
    b = ''
    for i in a[::-1]:
        b = i + b
        if i == '1':
            break
    ans = int(b, 2) ^ x
    print(ans if 0 < ans < x else -1)

H

题目大意

给一个由方向组成的序列,取出其方向子串,从(0,0)位置出发,按照方向子串的指示移动,求在移动过程中经过题目要求点的子串个数

题解

考虑选择的子串恰好能到达需要到达的位置,那么在这子串之后的位置均能被选择

如何求出这个子串,考虑后缀和,两个后缀和相减的结果就是这个子串到达的点的镜像,即需要取负,记录后缀和到达的位置的贡献,每次更新后缀和时就统计一次答案即可。

代码
# from collections import defaultdict
n, x, y = map(int, input().split())
a = input()
d = {(0, 0): n-1}
xk = yk = 0
ans = 0
if x == y == 0:
    print((n+1)*n//2)
else:
    for i in range(n - 1, -1, -1):
        if a[i] == 'W':
            yk += 1
        if a[i] == 'A':
            xk -= 1
        if a[i] == 'S':
            yk -= 1
        if a[i] == 'D':
            xk += 1
        d[(xk, yk)] = i - 1
        fx, fy = xk - x, yk - y
        k = d.get((fx, fy), n)
        ans += n - k
        # print(i,k,ans)

    print(ans)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值