A.Stickogon(贪心)
题意:
给你 n n n个长度为 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an的木棒。求你能同时拼出的正多边形(等边)的最大数量,使得:
- 多边形的每条边都正好由一根木棒组成。
- 多边形中使用的小棒不超过 1 1 1根。
注意:木棒不能折断。
分析:
本题我们贪心地考虑,尽可能找 3 3 3个长度一样的棍子凑成一个正多边形,然后统计个数。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
map<int, int> mp;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
mp[x]++;
}
int ans = 0;
for (auto s: mp) {
ans += s.second / 3;
}
cout << ans << endl;
}
return 0;
}
B.A BIT of a Construction(构造)
题意:
给定整数 n n n和 k k k,构造一个由 n n n个非负整数(即 ≥ 0 \geq 0 ≥0)组成的序列 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an,使得
- ∑ i = 1 n a i = k \sum\limits_{i=1}^n a_i=k i=1∑nai=k
- 使 a 1 ∣ a 2 ∣ … ∣ a n a_1 | a_2 | \ldots | a_n a1∣a2∣…∣an的二进制表示中 1 1 1的个数最大。
分析:
要求或运算 1 1 1的个数最大,显然最优的就是有最低到最高位都摆成连续的 1 1 1,我们尽可能考虑每一位上的 1 1 1都只出现一次,位数越低的 1 1 1代价越低,因此位数从小到大构造 1 1 1,直到无法构造为止。注意特判 n = 1 n=1 n=1的情况。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
if (n == 1) {
cout << m << endl;
} else {
int cnt = 1;
while (cnt * 2 + 1 <= m) {
cnt = cnt * 2 + 1;
}
cout << cnt << " " << m - cnt << " ";
for (int i = 2; i < n; i++) {
cout << 0 << " ";
}
cout << endl;
}
}
return 0;
}
C.How Does the Rook Move?(组合数学/动态规划)
题意:
给你一个 n × n n\times n n×n的棋盘,你和电脑轮流在棋盘上分别放置白车和黑车。在放置车的过程中,必须确保没有两个车会互相攻击。如果两只车共用同一行或同一列,无论颜色如何,都会互相攻击。
有效的一步棋是将一只车放在一个位置( r r r, c c c),使它不会攻击任何其他车。
你先下,当你在自己的回合中走了一步有效的棋,将白车下在位置( r r r, c c c)时,电脑会照搬你的棋,在它的回合中将黑车放在位置( c c c, r r r)。如果是 r = c r=c r=c,那么电脑就无法映射你的棋步,并跳过它的回合。
您已经与电脑下了 k k k步棋(电脑也会尝试复制这些棋步),你必须继续下棋直到没有剩余的有效棋步为止。在 k k k步之后继续下棋时,有多少种不同的最终情况是可能的?题目保证 k k k步和隐含的计算机棋步都是有效的。由于答案可能较大,答案对 1 0 9 + 7 10^9+7 109+7取模。
如果有一个坐标( r r r, c c c)在一种情况中有车,而在另一种情况中没有车,或者坐标上的车的颜色不同,那么这两种情况就被认为是不同的。
分析:
我们采用动态规划的思路,记先手白棋填满 n × n n×n n×n的空矩阵的方案数为 f n f_n fn,初始化 f 0 = f 1 = 1 f_0=f_1=1 f0=f1=1。
并不考虑此时放的是第几个棋子,而是考虑该矩阵的第 n n n行或列是如何被删去的:
若是被放置在 ( n , n ) (n,n) (n,n)的棋子删去,则仅需考虑如何填满剩下的 ( n − 1 ) × ( n − 1 ) (n−1)×(n−1) (n−1)×(n−1)的矩阵即可,则贡献为 f n − 1 f_{n−1} fn−1。
否则应有一白棋放置在 ( n , j ) (n,j) (n,j)或 ( j , n ) (j,n) (j,n) ( 1 ≤ j ≤ n − 1 ) (1≤j≤n−1) (1≤j≤n−1),方案数为 2 × ( n − i ) 2×(n−i) 2×(n−i),然后再考虑如何填满剩下的 ( n − 2 ) × ( n − 2 ) (n−2)×(n−2) (n−2)×(n−2)的矩阵,则贡献为 2 × ( n − 1 ) × f n − 2 2×(n−1)×f_{n−2} 2×(n−1)×fn−2。
则有转移方程:
∀
2
≤
i
,
f
i
=
f
i
−
1
+
2
×
(
i
−
1
)
×
f
i
−
2
∀2≤i,f_i=f_{i−1}+2×(i−1)×f_{i−2}
∀2≤i,fi=fi−1+2×(i−1)×fi−2
预处理即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod = 1e9 + 7;
const LL N = 3e5 + 5;
LL f[N];
void Init() {
f[0] = f[1] = 1;
for (int i = 2; i <= N; i++) {
f[i] = f[i - 1] + 2ll * (i - 1) * f[i - 2] % mod;
f[i] %= mod;
}
}
int main() {
Init();
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
for (int i = 1; i <= k; i++) {
int x, y;
cin >> x >> y;
if (x != y)
n -= 2;
else
n -= 1;
}
cout << f[n] << endl;
}
return 0;
}
D.A BIT of an Inequality(前缀和)
题意:
给你一个数组 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an。求这样的元组( x , y , z x,y,z x,y,z)的个数:
- 1 ≤ x ≤ y ≤ z ≤ n 1\leq x\leq y\leq z\leq n 1≤x≤y≤z≤n
- f ( x , y ) ⊕ f ( y , z ) > f ( x , z ) f(x,y)\oplus f(y,z)\gt f(x,z) f(x,y)⊕f(y,z)>f(x,z)
我们定义 f ( l , r ) = a l ⊕ a l + 1 ⊕ … ⊕ a r f(l,r)=a_l\oplus a_{l+1}\oplus\ldots\oplus a_{r} f(l,r)=al⊕al+1⊕…⊕ar
分析:
二进制拆位前缀和,只是维护的是到当前这位的 1 1 1的个数为奇数的个数。
维护出来之后对每个 y y y找最高位的 1 1 1,显然 f ( x , z ) f(x,z) f(x,z)和 f ( y , z ) f(y,z) f(y,z)的这一位的 1 1 1的个数之和只要为偶数,那么 f ( x , y ) ⊕ f ( y , z ) > f ( x , z ) f(x,y)\oplus f(y,z)\gt f(x,z) f(x,y)⊕f(y,z)>f(x,z),因为此时 f ( x , z ) f(x,z) f(x,z)这一位为 0 0 0了,而 f ( x , y ) ⊕ f ( y , z ) f(x,y)\oplus f(y,z) f(x,y)⊕f(y,z)这一位仍为 1 1 1。
显然为偶数的情况要么是奇数加奇数,要么是偶数加偶数。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod = 1000000007;
const LL N = 1e5 + 5;
LL dp[32][N];
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (auto &x: a)
cin >> x;
for (int i = 0; i < 31; i++) {
int sum = 0;
for (int j = 0; j < n; j++) {
sum = (sum + (a[j] >> i & 1)) & 1;
if (sum == 1) {
dp[i][j + 1] = dp[i][j] + 1;
} else {
dp[i][j + 1] = dp[i][j];
}
}
}
LL res = 0;
for (int i = 0; i < n; i++) {
int p = 0;
for (int j = 30; j >= 0; j--) {
if (a[i] >> j & 1) {
p = j;
break;
}
}
LL add1(1LL * dp[p][i] * (dp[p][n] - dp[p][i]));
LL add2(1LL * (i + 1 - dp[p][i]) * (n - i - (dp[p][n] - dp[p][i])));
res += add1 + add2;
}
cout << res << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。