牛客练习赛138


A. 小s的签到题

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    for (int i = 0; i < n; ++ i) {
    	char c;
    	std::cin >> c;
    	s += c;
    }

    char ans = 0;
    int max = 0;
    for (int i = 0; i < n; ++ i) {
    	std::string t;
    	std::cin >> t;
    	int x = 0;
    	for (auto & c : t) {
    		if (c == '/') {
    			break;
    		}
    		x = x * 10 + c - '0';
    	}
    	if (x > max) {
    		ans = s[i];
    		max = x;
    	}
    }
    std::cout << ans << "\n";
}

B. 行列改写

题意:你有长度为\(n\)和长度为\(m\)的数组\(r, c\)。填充一个\(n \times m\)的矩阵,每次选择一个没选择的行或列\(i\),把这一行或这一列都填上\(r_i\)\(c_i\)。求最后数组的最大元素和。

假设我们从最后一步开始填,那么如果倒数第\(i\)步之前填了\(x\)个行\(y\)个列,那么如果选择第\(i\)行,贡献为\((m - y + 1) \times r_i\),如果选择第\(j\)列贡献为\((n - x + 1) \times c_i\)。显然不管是填行还是列,我们应该选最大的填。假设这两个贡献都能吃满,那么如果填\(r_i\),这一步就损失了\(c_j\)的贡献,如果填\(c_j\),就损失了\(r_i\)的贡献。那么我们应该人损失的贡献最小,也就是\(r_i \geq c_j\)就填\(r_i\)。否则填\(c_j\)

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<i64> a(n), b(m);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 0; i < m; ++ i) {
    	std::cin >> b[i];
    }

    std::ranges::sort(a, std::greater<>());
    std::ranges::sort(b, std::greater<>());
    i64 ans = 0;
   	i64 i = 0, j = 0;
   	while (i < n && j < m) {
   		if (a[i] >= b[j]) {
   			ans += a[i] * (m - j);
   			++ i;
   		} else {
   			ans += b[j] * (n - i);
   			++ j;
   		}
   	}
   	std::cout << ans << "\n";
}

C. 树上替身追赶游戏

题意:两个人玩游戏,开始都在同一个点,每回合第一个人走到相邻的一个点,然后放置一个假人到这个点相邻的一个点。然后第二个人会往假人的方向走,如果和假人在同一个点就停留。如果任意时刻两个人在同一个点游戏结束。求最大的游戏回合数。

第一个人无法回头,只能一直往下走。那么答案就是距离起点最远的的点的距离加一。

点击查看代码
void solve() {
    int n, k;
    std::cin >> n >> k;
    -- k;
    std::vector<std::vector<int>> adj(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    std::queue<int> q;
    std::vector<int> d(n, n + 1);
    q.push(k);
    d[k] = 0;
    while (q.size()) {
    	int u = q.front(); q.pop();
    	for (auto & v : adj[u]) {
    		if (d[v] > d[u] + 1) {
    			d[v] = d[u] + 1;
    			q.push(v);
    		}
    	}
    }

    int ans = *std::max_element(d.begin(), d.end());
    std::cout << ans + 1 << "\n";
}

D. 全对!

题意:\(n\)个长度小于等于\(16\)\(01\)串。对于一个长度为\(L\)的字符串,如果第\(i\)个位置是\(1\),那么会在第\(k\times L + i\)时刻说好的。其中\(k\)是非负整数。求最小的时刻使得所有字符串都说好的。

把所有相同长度的字符串合到一起,记录\(st_l\)表示长度为\(l\)的所有字符串会在什么时刻一起回答的时刻。这个可以用二进制数存。然后观察到\([1, 16]\)的最小公倍数是\(720720\),那么最小时刻一定小于等于这个时间。那么可以用\(720720 \times 16\)的时间枚举出来哪个时刻所有长度的字符串都说好。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::string> s(n);
    for (int i = 0; i < n; ++ i) {
        std::cin >> s[i];
    }

    std::vector<int> st(17), vis(17);
    for (int i = 1; i <= 16; ++ i) {
        st[i] = (1 << i) - 1;
    }
    for (int i = 0; i < n; ++ i) {
        int len = s[i].size();
        vis[len] = 1;
        int x = 0;
        for (int j = 0; j < len; ++ j) {
            if (s[i][j] == '1') {
                x += 1 << j;
            }
        }

        st[len] &= x;
    }

    for (int i = 1; i <= 16; ++ i) {
        if (vis[i] && st[i] == 0) {
            std::cout << -1 << "\n";
            return;
        }
    }

    int m = 1;
    for (int i = 1; i <= 16; ++ i) {
        if (vis[i]) {
            m = std::lcm(m, i);
        }
    }

    for (int t = 1; t <= m; ++ t) {
        bool flag = true;
        for (int i = 1; i <= 16; ++ i) {
            if (!vis[i]) {
                continue;
            }

            int p = t % i == 0 ? i - 1 : t % i - 1;
            if (~st[i] >> p & 1) {
                flag = false;
                break;
            }
        }

        if (flag) {
            std::cout << t << "\n";
            return;
        }
    }
    std::cout << -1 << "\n";
}

E. 图上替身追赶游戏

题意:比\(c\)多了两个条件:一个是两个人可以重合,一个是你可以加一条边,选完假人后第二个人在所有可以选的点里选编号最小的。求有多少加边方案使得两个人可以在某一时刻不重合不相邻。

首先加一条边显然会形成一个环。那么我们肯定要走到这个环里才能实现不相邻。然后手玩一下发现,只有边数为\(4, 5, 6\)的环才能使得不相邻,那么只要求出有多少形成\(4, 5, 6\)长度环的方案就行。
赛时写了个觉得挺对的\(dsu \ on \ tree\),但没过。
实际上树形\(dp\)就行了,记\(f[u][i]\)\(u\)的子树里距离它\(0/1/2/3/4/5/\)的点的个数。那么只需要一边跑子树,把这棵子树的贡献和前面的子树的贡献算一下就行了,就是拿两条链加一条边形成一个环。对于长度为\(i\)的环,只要算所有\(\sum_{j=1}^{i} f[u][j] \times f[v][i - j]\),注意\(u, v\)之间有一条边要算上,然后\(f[u][i + 1] += f[v][i]\)加上个数。

点击查看代码
void solve() {
    int n, k;
    std::cin >> n >> k;
    -- k;
    std::vector<std::vector<int>> adj(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    std::vector f(n, std::array<i64, 6>{});
    i64 ans = 0;
    auto dfs = [&](auto & self, int u, int fa) -> void {
    	f[u][0] = 1;
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		self(self, v, u);
    		for (int i = 1; i <= 2; ++ i) {
    			ans += f[u][i] * f[v][2 - i];
    		}

    		for (int i = 1; i <= 3; ++ i) {
    			ans += f[u][i] * f[v][3 - i];
    		}

    		for (int i = 1; i <= 4; ++ i) {
    			ans += f[u][i] * f[v][4 - i];
    		}

    		for (int i = 0; i + 1 < 6; ++ i) {
    			f[u][i + 1] += f[v][i];
    		}
    	}

    	ans += f[u][3] + f[u][4] + f[u][5];
    };

   	dfs(dfs, k, -1);
   	std::cout << ans << "\n";
}
posted @ 2025-05-09 21:59  maburb  阅读(52)  评论(0)    收藏  举报