题目链接:
https://ac.nowcoder.com/acm/contest/10272
A.Ah, It's Yesterday Once More
题意:
构造一个小于 20 * 20 的 01 矩阵,1 表示有一个空地,空地上有一个袋鼠,0 表示墙,袋鼠不能经过墙,保证空地是相连的,同时不存在环,系统会随机给出长度最多为 5e4 的字符串,整个矩阵的袋鼠会根据指令同时向上下左右移动,操作结束后,如果有任意两只袋鼠不处在同一个位置,那么 hack 成功,要求构造的矩阵可以 hack 掉尽可能多的数据。
思路:
因为距离越长也就意味着袋鼠越难汇合,所以构造一条从右上角一直走到左下角的曲折路线(最长路径),同时,尽可能多的增加空地和死胡同,让袋鼠汇合的难度增加。
以下为一个构造,总共 268 个 1。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
vector <string> s = {
"11011111011111011111",
"10110100110100110101",
"11101101101101101101",
"10011011011011011010",
"10110110110110110111",
"01101101101101101101",
"11011011011011011011",
"10110110110110110110",
"11101101101101101101",
"10011011011011011001",
"10110110110110110111",
"01101101101101101101",
"11011011011011011011",
"10110110110110110110",
"11101101101101101101",
"10011011011011011001",
"10110110110110110111",
"01101101101101101101",
"11011001011001011011",
"01110111110111110110"
};
cout << "20 20\n";
for (int i = 0; i < 20; i ++ )
cout << s[i] << "\n";
return 0;
}
E. Evil Coordinate
题意:
有一个机器人在(0,0)位置,有一串指令,机器人会按照这个指令行动,宝藏被埋在(\(m_x, m_y\))处,现在对指令重新排列,问有没有一种路径可以不经过宝藏被埋地。
思路:
将向同一个方向运动的指令连在一起执行,所有组合方式都跑一遍即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int mx, my;
string s;
cin >> mx >> my >> s;
string st = "LRUD";
vector <int> d = {0, 1, 2, 3}, cnt(4);
for (int i = 0; i < 4; i ++ )
cnt[i] = count(s.begin(), s.end(), st[i]);
auto check = [&](string s){
int x = 0, y = 0;
if (x == mx && y == my) return false;
for (auto c : s){
if (c == 'L') x -- ;
else if (c == 'R') x ++ ;
else if (c == 'U') y ++ ;
else y -- ;
if (x == mx && y == my) return false;
}
return true;
};
do{
string t = "";
for (int i = 0; i < 4; i ++ )
t += string(cnt[d[i]], st[d[i]]);
if (check(t)){
cout << t << "\n";
return;
}
}while(next_permutation(d.begin(), d.end()));
cout << "Impossible\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
F.Fireworks
题意:
做一个烟花需要花费 \(n\) 的时间,放掉现在已经造的烟花(不论造了多少个)需要花费 \(m\) 的时间,一个烟花成功绽放的概率为 \(\frac{p}{10000}\),问最优策略下,让一个烟花成功释放的期望为多少。
思路:
假设做 \(x\) 个烟花是最优的策略,设造一个烟花的失败概率为 \(q\)。
那么得到答案的期望就是 \(f(x) = \frac{nx + m}{1 - q^x}\)。
f'(x) = \(\frac{n(1 - q^x) + (nx + m)lnq * q^x}{(1 - q^x)^2}\)
令 g(x) = \(n(1 - q^x) + (nx + m)lnq * q^x\)。
g'(x) = \(ln^2q(nx + m)q^x\)
因为 g'(x) 恒大于 0,所有 g(x) 单调递增。
\(\lim_{x \to 0} g(x) < 0\),所以 f(x) 是先减后增的。
可以通过三分求解凸函数。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int n, m, p;
cin >> n >> m >> p;
double q = 1 - 1.0 * p / 10000;
auto cal = [&](double x){
return (double)(x * n + m) / (1 - pow(q, x));
};
double L = 1, R = 1e18;
while(L + 10 < R){
double mid1 = L + (R - L) / 3.0;
double mid2 = R - (R - L) / 3.0;
if (cal(mid1) > cal(mid2)) L = mid1;
else R = mid2;
}
double ans = cal(L);
for (int i = L + 1; i <= R; i ++ )
ans = min(ans, cal(i));
cout << fixed << setprecision(10) << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
K.K Co-prime Permutation
题意:
给定一个排列,可以随意调换元素位置,要求构造一个序列,使得元素与其下标的 \(gcd = 1\) 的数量为 \(k\) 个。
思路:
对于一个排列,因为有 1,所以不可避免的,数量至少为 1,交换两个相邻的数,就可以让 \(gcd() = 1\) 的数量 + 2。由此构造出题目要求的序列。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n, k;
cin >> n >> k;
if (k == 0) cout << "-1\n";
else{
vector <int> a(n + 1);
iota(a.begin(), a.end(), 0);
for (int i = k; i >= 2; i -= 2)
swap(a[i], a[i - 1]);
for (int i = 1; i <= n; i ++ )
cout << a[i] << " \n"[i == n];
}
return 0;
}
L.Let's Play Curling
题意:
给定 \(n\) 个红色方的冰壶的位置以及 \(m\) 个蓝色方的冰壶的位置,红色方有多少个冰壶的位置距离中心比蓝色方任何一个冰壶距离中心的位置都要小,就获得多少分,现在中心位置未知,问最多能获得多少分。
思路:
将所有坐标存在一起,相同位置时,蓝色方冰壶放在红色放前面。每次碰到蓝色方即计算它与之前蓝色方中间有多少个红色方的冰壶即可。因为只要将中心定在正中间,那么这个范围中所有红色方冰壶都是符合条件的。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int n, m;
cin >> n >> m;
vector < pair<int, int> > a;
for (int i = 1; i <= n; i ++ ){
int x;
cin >> x;
a.push_back({x, 2});
}
for (int i = 1; i <= m; i ++ ){
int x;
cin >> x;
a.push_back({x, 1});
}
sort(a.begin(), a.end());
int sz = a.size(), cnt = 0, ans = 0;
for (int i = 0; i < sz; i ++ ){
auto [x, y] = a[i];
if (y == 2) cnt ++ ;
if (y == 1){
ans = max(ans, cnt);
cnt = 0;
int j = i;
while(j < sz - 1 && a[j].first == a[j + 1].first){
j ++ ;
}
i = j;
}
}
ans = max(ans, cnt);
if (ans == 0) cout << "Impossible\n";
else cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
M.Monster Hunter
题意:
有一颗 \(n\) 个节点的树,每个点有一个权值,删除一个点需要花费这个点的权值 + 它的子节点的权值之和,现在有一个魔法可以免费删除一个节点,问使用 0,1,2... \(n\) 次魔法删除整棵树的最小代价分别是多少。
思路:
一个点删除之后造成的影响和它的子节点是否存在有关,所以定义 \(dp[i][j][k]\) 表示第 \(i\) 个节点为根的子树,剩余 \(j\) 个节点的最小花费,\(k\) 为 0 表示这个节点被保留,否则表示被删除。接着就是树上 01 背包的一个过程。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL inf = 1e18;
void solve(){
int n;
cin >> n;
vector < vector <int> > e(n + 1);
for (int i = 2; i <= n; i ++ ){
int j;
cin >> j;
e[j].push_back(i);
}
vector <LL> hp(n + 1);
for (int i = 1; i <= n; i ++ )
cin >> hp[i];
vector < vector < array<LL, 2> > > dp(n + 1, vector < array<LL, 2> >(n + 1, {inf, inf}));
vector <int> sz(n + 1);
function<void(int)> dfs = [&](int u){
sz[u] = 1;
dp[u][0][0] = 0;
dp[u][1][1] = hp[u];
for (auto v : e[u]){
dfs(v);
for (int i = sz[u]; i >= 0; i -- ){ //枚举当前节点有多少个节点
for (int j = sz[v]; j >= 0; j -- ){ //子儿子能有多少个节点
dp[u][i + j][0] = min(dp[u][i + j][0], dp[u][i][0] + min(dp[v][j][0], dp[v][j][1]));
dp[u][i + j][1] = min(dp[u][i + j][1], dp[u][i][1] + min(dp[v][j][0], dp[v][j][1] + hp[v])); //如果父子节点同时保留,那么会多 hp[v] 的花费
}
}
sz[u] += sz[v];
}
};
dfs(1);
for (int i = n; i >= 0; i -- )
cout << min(dp[1][i][0], dp[1][i][1]) << " \n"[i == 0];
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}