比赛链接:
https://ac.nowcoder.com/acm/contest/33191
A.Array
题意:
给定一个长为 \(n\) 的序列 \(a\),满足 \(\sum_{i = 1}^{n} \frac{1}{a_i} <= \frac{1}{2}\),要求构造一个循环序列 \(b\),使得每 \(a_i\) 位 \(i\) 都至少出现一次。
思路:
先转化一下不等式。
\(\sum_{i = 1}^{n} \frac{1}{a_i} <= \frac{1}{2}\)
= \(\sum_{i = 1}^{n} \frac{1}{\frac{a_i}{2}} <= 1\)
= \(\sum_{i = 1}^{n} \frac{1}{2^{\lfloor log_{2} a_i \rfloor}} <= \sum_{i = 1}^{n} \frac{1}{\frac{a_i}{2}} <= 1\)
所以可以将 \(a_i\) 先转化为 \(2^{\lfloor log_{2} a_i \rfloor}\),接着对于每 \(2^{\lfloor log_{2} a_i \rfloor}\) 位都要放入一个 \(i\),所以先考虑对 \(2^{\lfloor log_{2} a_i \rfloor}\) 排序,然后由小到大去构造这个序列。
每次一定能保证有位置放 \(i\),具体可以看题解证明。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n;
cin >> n;
vector <int> a(n + 1);
vector < pair<int, int> > b(n + 1);
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
int x = 1;
while(x * 2 <= a[i]){
x *= 2;
}
b[i].first = x;
b[i].second = i;
}
sort(b.begin() + 1, b.end());
int pos = 1;
vector <int> ans(N);
for (int i = 1; i <= n; i ++ ){
for (int j = pos; j <= N - 10; j += b[i].first)
ans[j] = b[i].second;
while(ans[pos]){
pos ++ ;
}
}
cout << N - 10 << "\n";
for (int i = 1; i <= N - 10; i ++ ){
if (ans[i]) cout << ans[i];
else cout << 1;
cout << " \n"[i == N - 10];
}
return 0;
}
B.Eezie and Pie
题意:
\(n\) 个节点的以 1 为根的树,每个节点可以向它的 0 到 \(d[i]\) 级祖先贡献 1 的价值,输出每个点的价值。
思路:
通过树上差分去求解,从根往下搜,记录路径,将每个节点对祖先的贡献通过差分数组记录,然后进行前缀和即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n;
cin >> n;
vector < vector<int> > e(n + 1);
for (int i = 0; i < n - 1; i ++ ){
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
vector <int> d(n + 1);
for (int i = 1; i <= n; i ++ )
cin >> d[i];
vector <int> dep(n + 1), sum(n + 1), a(n + 1);
function<void(int, int)> dfs = [&](int u, int fa){
dep[u] = dep[fa] + 1;
a[dep[u]] = u;
sum[u] ++ ;
sum[a[max(0, dep[u] - d[u] - 1)]] -- ;
for (auto v : e[u]){
if (v == fa) continue;
dfs(v, u);
sum[u] += sum[v];
}
};
dfs(1, 0);
for (int i = 1; i <= n; i ++ )
cout << sum[i] << " \n"[i == n];
return 0;
}
G.Icon Design
题意:
输出指定大小的图案。
思路:
按题意模拟即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n;
cin >> n;
vector a(4 * n + 6, vector<char>(13 * n + 20, '.'));
//*
for (int j = 1; j <= 13 * n + 19; j ++ )
a[1][j] = a[4 * n + 5][j] = '*';
for (int i = 1; i <= 4 * n + 5; i ++ )
a[i][1] = a[i][13 * n + 19] = '*';
//N
for (int i = n + 2; i <= 3 * n + 4; i ++ )
a[i][n + 3] = a[i][3 * n + 5] = '@';
for (int i = n + 2; i <= 3 * n + 4; i ++ )
a[i][i + 1] = '@';
//F
for (int i = n + 2; i <= 3 * n + 4; i ++ )
a[i][4 * n + 7] = '@';
for (int j = 4 * n + 7; j <= 6 * n + 9; j ++ )
a[n + 2][j] = a[2 * n + 3][j] = '@';
//L
for (int i = n + 2; i <= 3 * n + 4; i ++ )
a[i][7 * n + 11] = '@';
for (int j = 7 * n + 11; j <= 9 * n + 13; j ++ )
a[3 * n + 4][j] = '@';
//S
for (int i = n + 2; i <= 2 * n + 3; i ++ )
a[i][10 * n + 15] = '@';
for (int i = 2 * n + 3; i <= 3 * n + 4; i ++ )
a[i][12 * n + 17] = '@';
for (int j = 10 * n + 15; j <= 12 * n + 17; j ++ )
a[n + 2][j] = a[2 * n + 3][j] = a[3 * n + 4][j] = '@';
//output
for (int i = 1; i <= 4 * n + 5; i ++ ){
for (int j = 1; j <= 13 * n + 19; j ++ )
cout << a[i][j];
cout << "\n";
}
return 0;
}
J.Number Game
题意:
有三个数 \(A, B, C\),每次可以让 \(B = A - B\) 或 \(C = B - C\),问进行若干次操作后能否得到 \(x\)。
思路:
某种操作连续进行两次后就等于没操作,所以两个操作一定是轮流进行的,那么答案只可能有两种。
\(C + k * (A - 2 * B)\) 或 \(B - C + k * (A - 2 * B)\),要特判一下 \(A - 2 * B\) 的情况。
代码:
#include <bits/stdc++.h>
using namespace std;
void solve(){
int A, B, C, x;
cin >> A >> B >> C >> x;
if (A == 2 * B){
if (x == C || x == B - C) cout << "Yes\n";
else cout << "No\n";
}
else{
if ((x - C) % (A - 2 * B) == 0 || (x - B + C) % (A - 2 * B) == 0) cout << "Yes\n";
else cout << "No\n";
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
M.Z-Game on grid
题意:
两人轮流在 \(n * m\) 的格子上移动棋子,刚开始棋子在 (1, 1),每次可以向某一维度的正方向移动,如果到达了标有 'A' 的格子,则先手胜,到达了标有 'B' 的格子,后手胜,如果没碰到任何有标志的格子,最后到到了 \((n, m)\),则平局。问在后手移动方向不确定的时候,先手能否保证先手胜、平局或后手胜。
思路:
如果反过来走的话,那么每次移动之后,没有后效性,所以用动态规划。
定义 \(dp[i][j]\) 表示在 \((i, j)\) 这个点的时候,能否保证一个结局。定义先手胜的情况为 1,平局为 4,后手胜为 2。
最后只需要判断 \(dp[1][1]\) & 上各自的情况是否大于 0,即可知道能否保证该种情况发生。
对于 \(dp[i][j]\),如果先手移动,只要有一个方向的结果符合自己的要求,就能保证这种情况的发生,得到 \(dp[i][j] = dp[i][j + 1]\) | \(dp[i + 1][j]\)。如果该后手移动了,只有当两个方向都是自己想要的结果,才能保证这种情况的发生,得到 \(dp[i][j] = dp[i][j + 1]\) & \(dp[i + 1][j]\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 5e2 + 10;
char a[N][N];
int dp[N][N];
void solve(){
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
cin >> a[i][j];
for (int i = n; i >= 1; i -- ){
for (int j = m; j >= 1; j -- ){
if (a[i][j] == 'A') dp[i][j] = 1;
else if (a[i][j] == 'B') dp[i][j] = 2;
else if (i == n && j == m) dp[i][j] = 4;
else if (i == n) dp[i][j] = dp[i][j + 1];
else if (j == m) dp[i][j] = dp[i + 1][j];
else if ((i + j) % 2 == 0) dp[i][j] = dp[i][j + 1] | dp[i + 1][j];
else dp[i][j] = dp[i][j + 1] & dp[i + 1][j];
}
}
array t{1, 4, 2};
for (int i = 0; i < 3; i ++ ){
if (dp[1][1] & t[i]) cout << "yes";
else cout << "no";
cout << " \n"[i == 2];
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}