Codeforces Round #675 (Div. 2)、Codeforces Round #676 (Div. 2)
A. XORwice
已知 a , b a, b a,b,对于任意的 x x x,计算 ( a ⊕ x ) + ( b ⊕ x ) (a\oplus x)+(b\oplus x) (a⊕x)+(b⊕x)的最小值。
a | b | x | x | sum | sum |
---|---|---|---|---|---|
0 | 0 | 0 | 1 | 0 | 2 |
0 | 1 | 0 | 1 | 1 | 1 |
1 | 0 | 0 | 1 | 1 | 1 |
1 | 1 | 0 | 1 | 2 | 0 |
对于 a a a、 b b b的任意一位,只有当 a a a、 b b b的该位同时为 1 1 1的时候 x x x的该位为 1 1 1才能让该位的和比 x x x的该位为 0 0 0的时候小。
# include <iostream>
# include <cstdio>
void solve() {
int a, b;
scanf("%d%d", &a, &b);
int x = a & b;
int ans = (a ^ x) + (b ^ x);
printf("%d\n", ans);
}
int main() {
int T;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
另外,位运算中还有一个公式: a + b = a ⊕ b + ( a & b ) ∗ 2 a+b=a\oplus b +(a \&b) * 2 a+b=a⊕b+(a&b)∗2
此公式中的+
代表加法,*
代表乘法
证明:二进制计算中, 0 + 0 = 0 0+0=0 0+0=0, 0 + 1 = 1 0+1=1 0+1=1, 1 + 0 = 1 1+0=1 1+0=1, 1 + 1 = 0 ′ 1+1=0' 1+1=0′,但 1 + 1 = 0 1+1=0 1+1=0需要向高位进一位,相当于加上 ( a & b ) < < 1 (a\&b)<<1 (a&b)<<1。
另外,如果 a + b ≥ ( a & b ) ∗ 2 a+b\ge (a\&b)*2 a+b≥(a&b)∗2并且 a ⊕ b a\oplus b a⊕b的每一个二进制位都和 a & b a\&b a&b不同时为1,那么 a ⊕ b = a + b − ( a & b ) ∗ 2 a\oplus b=a+b-(a\&b)*2 a⊕b=a+b−(a&b)∗2。
B. Putting Bricks in the Wall
给定一个由 0 0 0和 1 1 1构成的 n × n n×n n×n的矩阵,有一个人从起点 ( 1 , 1 ) (1,1) (1,1)走到终点 ( n , n ) (n, n) (n,n),他只能够走相同的数字,要么全部路过元素为 1 1 1的格子,要么全部路过元素为 0 0 0的格子。
现最多给两次操作,每一次操作可以选择该数组的一个元素(除了起点和终点以外)从 0 0 0变成 1 1 1,也可以选择该数组的一个元素从 1 1 1变成 0 0 0。在操作后使得走迷宫的人不能走到终点。输出任意一种方案。
S | A | ||
---|---|---|---|
B | |||
D | |||
C | F |
只需要将A,B,C,D中的AB全变为0,CD全变为1,或者AB全变为1,CD全变为0即可。
# include <iostream>
# include <cstdio>
const int N = 205;
char a[N][N];
void solve() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", a[i] + 1);
}
int A, B, C, D;
A = a[1][2] - '0';
B = a[2][1] - '0';
C = a[n][n-1] - '0';
D = a[n-1][n] - '0';
int num = (A << 3) + (B << 2) + (C << 1) + D;
switch (num) {
case 0: {
printf("2\n1 2\n2 1\n");
break;
}
case 1: {
printf("1\n%d %d\n", n, n-1);
break;
}
case 2: {
printf("1\n%d %d\n", n-1, n);
break;
}
case 3: {
puts("0");
break;
}
case 4: {
printf("1\n1 2\n");
break;
}
case 5: {
printf("2\n2 1\n%d %d\n", n, n-1);
break;
}
case 6: {
printf("2\n2 1\n%d %d\n", n-1, n);
break;
}
case 7: {
printf("1\n2 1\n");
break;
}
case 8: {
printf("1\n2 1\n");
break;
}
case 9: {
printf("2\n2 1\n%d %d\n", n - 1, n);
break;
}
case 10: {
printf("2\n1 2\n%d %d\n", n - 1, n);
break;
}
case 11: {
printf("1\n1 2\n");
break;
}
case 12: {
printf("0\n");
break;
}
case 13: {
printf("1\n%d %d\n", n-1, n);
break;
}
case 14: {
printf("1\n%d %d\n", n, n-1);
break;
}
case 15: {
printf("2\n1 2\n2 1\n");
break;
}
}
}
int main() {
int T;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
C. Palindromifier
有一个仅由英文小写字母组成的长度为 n n n的字符串 s s s,对该字符串进行 k k k操作,每次操作可以选择以下两种操作中的一种:
操作 L L L:选择一个下标 i ( 2 ≤ i ≤ n − 1 ) i(2\le i\le n-1) i(2≤i≤n−1),将 s 2 s 3 . . . s i s_2s_3...s_i s2s3...si复制(append)下来,然后倒置,再将其放到字符串的开头。
操作 R R R:选择一个下标 i ( 2 ≤ i ≤ n − 1 ) i(2\le i\le n-1) i(2≤i≤n−1),将 s i s i + 1 . . . s n − 1 s_is_{i+1}...s_{n-1} sisi+1...sn−1复制(append)下来,然后倒置,再将其放到字符串的末尾。
0 ≤ k ≤ 30 0\le k \le30 0≤k≤30,目标是将字符串转为回文串。输出任意一种情况均可。
假设字符串是 a b c d e abcde abcde,经过 L n L\ n L n的操作后会变成 d c b a b c d e dcbabcde dcbabcde。这个字符串除了 e e e以外其余都是对称的。如果先将原字符串 a b c d e abcde abcde经过 R n − 1 R\ n-1 R n−1操作后变成 a b c d e d abcded abcded,再经过 L n L\ n L n的操作得到 e d c b a b c d e d edcbabcded edcbabcded,再经过 L 2 L\ 2 L 2就可以得到 d e d c b a b c d e d dedcbabcded dedcbabcded,这是一个回文字符串。其余的字符串也能通过此方法得到回文字符串。
答案就是
3
R n-1
L n
L 2
# include <iostream>
# include <string>
int main() {
std::string s;
std::cin >> s;
int n = s.size();
std::cout << "3\nR " << n - 1 << "\nL " << n << "\nL 2";
return 0;
}
D. Hexagons
有一个正六边形构成的蜂窝网,每一个正六边形对应一个坐标,坐标的表示形式如图所示。
共有6个方向向量 C 1 ( 1 , 1 ) C_1(1, 1) C1(1,1), C 2 ( 0 , 1 ) C_2(0,1) C2(0,1), C 3 ( − 1 , 0 ) C_3(-1,0) C3(−1,0), C 4 ( − 1 , − 1 ) C_4(-1,-1) C4(−1,−1), C 5 ( 0 , − 1 ) C_5(0,-1) C5(0,−1), C 6 ( 1 , 0 ) C_6(1,0) C6(1,0),每个方向向量有一个权重,代表往该方向走的花费。求从起点 ( 0 , 0 ) (0,0) (0,0)到终点 ( x , y ) (x,y) (x,y)的最小花费。
从原点到任意一点的最短路线仅仅只需要使用两个方向向量若干次。证明:假设目标点在原点的右边(在其他方向的证明过程也类似。),那么只需要 C 1 C_1 C1、 C 2 C_2 C2、 C 3 C_3 C3就可以到达该目标点,如果使用 C 4 C_4 C4、 C 5 C_5 C5、 C 6 C_6 C6就会和 C 1 C_1 C1、 C 2 C_2 C2、 C 3 C_3 C3相抵消,只会增加距离。而 C 2 C_2 C2刚好等于先走一步 C 1 C_1 C1再走一步 C 3 C_3 C3,因此如果 C 1 + C 3 < C 2 C_1+C_3 < C_2 C1+C3<C2那么就在需要走 C 2 C_2 C2时换成走一步 C 1 C_1 C1再走一步 C 3 C_3 C3,如果 C 1 + C 3 ≥ C 2 C_1+C_3 \ge C_2 C1+C3≥C2那么就在 C 1 C_1 C1和 C 3 C_3 C3都存在时将 C 1 C_1 C1和 C 3 C_3 C3替换成 C 2 C_2 C2,那么就只需要若干步 C 1 C_1 C1和若干步 C 2 C_2 C2 或者 若干步 C 3 C_3 C3和若干步 C 2 C_2 C2就可以以最少的代价到达目标点。
假设目标点的坐标为 ( x , y ) (x, y) (x,y),需要选择的方向向量为 ( i x , i y ) (i_x,i_y) (ix,iy)和 ( j x , j y ) (j_x,j_y) (jx,jy),需要走 k 1 k_1 k1步的 i i i和 k 2 k_2 k2步的 j j j,那么
( x , y ) = k 1 ∗ ( i x , i y ) + k 2 ∗ ( j x , j y ) (x,y)=k_1*(i_x,i_y)+k_2*(j_x,j_y) (x,y)=k1∗(ix,iy)+k2∗(jx,jy)
{ x = k 1 ∗ i x + k 2 ∗ j x y = k 1 ∗ i y + k 2 ∗ j y \left\{ \begin{aligned} x = k_1*i_x+k_2*j_x \\ y=k1*i_y+k2*j_y \end{aligned} \right. { x=k1∗ix+k2∗jxy=k1∗iy+k2∗jy
k 1 = ∣ x j x y j y ∣ ∣ i x j x i y j y ∣ k 2 = ∣ x j x y j y ∣ ∣ i x j x i y j y ∣ k_1=\frac{\left|\begin{array}{cccc} x & j_x \\ y & j_y \end{array}\right| }{\left|\begin{array}{cccc} i_x & j_x \\ i_y & j_y \end{array}\right| }\quad k_2=\frac{\left|\begin{array}{cccc} x & j_x \\ y & j_y \end{array}\right| }{\left|\begin{array}{cccc} i_x & j_x \\ i_y & j_y \end{array}\right| } k1=∣∣∣∣ixiyjxjy∣∣∣∣∣∣∣∣xyjxjy∣∣∣∣k2=∣∣∣∣ixiyjxjy∣∣∣∣∣∣∣∣xyjxjy∣∣∣∣
枚举所有可以选择的方向向量对,删除方向相反的方向向量对,删除 ∣ i x j x i y j y ∣ = 0 \left|\begin{array}{cccc} i_x & j_x \\ i_y & j_y \end{array}\right|=0 ∣∣∣∣ixiyjxjy∣∣∣∣=0的方向向量对,删除 k 1 k_1 k1或 k 2 k_2 k2小于 0 0 0的方向向量对,剩下的所有方案中选最小值。
# include <iostream>
typedef long long ll;
const int dx[6] = {
1, 0, -1, -1, 0, 1};
const int dy[6] = {
1, 1, 0, -1, -1, 0};
int weight[6];
void solve() {
int x, y;
scanf("%d%d", &x, &y);
for (int i = 0; i < 6; i++) {
scanf("%d", &weight[i]);
}
ll min = 4e18;
for (int i = 0; i < 6; i++) {
for (int j = i + 1; j < 6; j++) {
if (j - i == 3) {
continue;
}
int delta = dx[i] * dy[j] - dx[j] * dy[i];
if (delta == 0) {
continue;
}
int delta_a = x * dy[j] - dx[j] * y;
int k1 = delta_a / delta;
int delta_b = y * dx[i] - dy[i] * x;
int k2 = delta_b / delta;
if (k1 < 0 || k2 < 0) {
continue;
}
ll ans = 1ll * k1 * weight[i] + 1ll * k2 * weight[j];
min = std::min(min, ans);
}
}
printf("%lld\n", min);
}
int main() {
int T;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
A. Fence
给定三条边 a a a, b b b, c c c,求边长 d d d,使得 a a a、 b b b、 c c c、 d d d能够构成一个非退化四边形(non-degenerate simple quadrilateral)。非退化四边形指的是四边形的四个角没有0度或者180度的情况。
输出 m a x ( a , b , c ) max(a, b, c) max(a,b,c)~ ( a + b + c − 1 ) (a+b+c-1) (a+b+c−1)之间的任意一个值都可。
# include <iostream>
# include <cstdio>
# include <algorithm>
# include <ctime>
typedef long long ll;
int random() {
return (rand() << 15) | rand();
}
// [0, n)
int random(int n) {
return random() % n;
}
// [l, r)
int random(const int &l, const int &r) {
if (l == r) {
return l;
}
return l + random(r - l);
}
long long randomll() {
return ((long long)random() << 30) | random();
}
long long randomll(const long long &n) {
return randomll() % n;
}
long long randomll(const long long &l, const long long &r) {
if (l == r) {
return l;
}
return l + randomll(r - l);
}
long double randoml() {
return (long double)randomll() / (1ll << 60);
}
long double randoml(const long double &n) {
return randoml() * n;
}
long double randoml(const long double &l, const long double &r) {
return l + randoml(r - l);
}
void solve() {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
ll l = std::max(std::max(a, b), c);
ll r = 1ll * a + b + c;
ll ans = randomll(l, r);
printf("%lld\n", ans);
}
int main() {
int T;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
B. Nice Matrix
给定一个 n × m n×m n×m的矩阵,每一次操作可以将该矩阵内的一个元素+1或者-1,求最小操作次数使得该矩阵的每一行、每一列都变成回文序列。
如果矩阵的每一行、每一列都是回文序列,那么 a [ i ] [ j ] = a [ i ] [ m − j − 1 ] = a [ n − i − 1 ] [ j ] a[i][j]=a[i][m-j-1]=a[n-i-1][j] a[i][j]=a[i][m−j−1]=a[n−i−1][j]。对三个数进行+1或者-1的操作使得三个数相等,花费最小,最优的方法是另外两个数通过+1或者-1变成中位数的值。对矩阵的每一个元素都进行此操作,记录下每一次操作的花费,求和即是答案。
# include <iostream>
# include <cstdio>
# include <vector>
# include <algorithm>
typedef long long ll;
int a[105][105];
void solve() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++)
scanf("%d", &a[i][j]);
}
ll ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
std::vector<int> b;
b.push_back(a[i][j]);
b.push_back(a[i][m-j-1]);
b.push_back(a[n-i-1][j]);
std::sort(b.begin(), b.end());
a[i][j] = a[i][m - j - 1] = a[n - i - 1][j] = b[1];
ans += 1ll * b[2] - b[1] + b[1] - b[0];
}
}
printf("%lld\n", ans);
}
int main() {
int T;
std::cin >> T;
while (T--){
solve();
}
return 0;
}
C. Bargain
给定一个数字 n ( 1 ≤ n ≤ 1 0 1 0 5 ) n(1\le n\le 10^{10^5}) n(1≤n≤10105),求 n n n的所有子字符串的和(不含原子串,但包含空串)。
枚举每个字符,考虑这一位能够将答案的和增加多少。
该位可增加的值分为:删除的区间在该位之前的贡献和删除的区间在该位之后的贡献。
以左边的数字为高位,右边的数字为低位,从0开始计数。
删除的区间在该位之前的贡献为 s [ i ] ∗ 1 0 i ∗ ( n − i ) ∗ ( n − i + 1 ) 2 s[i]*10^i*\frac{(n-i)*(n-i+1)}{2} s[i]∗10i∗2(n−i)∗(n−i+1)
删除的区间在该位之后的贡献为 i ∗ 1 0 i − 1 + ( i − 1 ) ∗ 1 0 i − 2 + . . . + 2 ∗ 1 0 1 + 1 ∗ 1 0 0 i*10^{i-1}+(i-1)*10^{i-2}+...+2*10^1+1*10^0 i∗10i−1+(i−1)∗10i−2+...+2∗101+1∗100。
令 \quad \quad S ( n ) = 1 ∗ 1 0 0 + 2 ∗ 1 0 1 + . . . + ( n − 1 ) ∗ 1 0 n − 2 + n ∗ 1 0 n − 1 S(n)=1*10^0+2*10^1+...+(n-1)*10^{n-2}+n*10^{n-1} S(n)=1∗100+2∗101+...+(n−1)∗10n−2+n∗10n−1
\quad 10 ∗ S ( n ) = 1 ∗ 1 0 1 + . . . + ( n − 2 ) ∗ 1 0 n − 1 + ( n − 1 ) ∗ 1 0 n − 1 + n ∗ 1 0 n 10*S(n)=\quad\quad \quad \quad1*10^1+...+(n-2)*10^{n-1}+(n-1)*10^{n-1}+n*10^n 10∗S(n)=1∗101+...+(n−2)∗10n−1+(n−1)∗10n−1+n∗10n
− 9 ∗ S ( n ) = 1 ∗ 1 0 0 + 1 ∗ 1 0 1 + . . . + 1 ∗ 1 0 n − 2 + 1 ∗ 1 0 n − 1 − n ∗ 1 0 n \quad-9*S(n)=1*10^0+1*10^1+...+1*10^{n-2}+1*10^{n-1}-n*10^n −9∗S(n)=1∗100+1∗101+...+1∗10n−2+1∗10n−1−n∗10n
− 9 ∗ S ( n ) = 1 0 n − 1 9 − n ∗ 1 0 n \quad-9*S(n)=\frac{10^n-1}{9}-n*10^n −9∗S(n)=910n−1−n∗10n
S ( n ) = ( 9 ∗ n − 1 ) ∗ 1 0 n + 1 81 S(n)=\frac{(9*n-1)*10^n+1}{81} S(n)=81(9∗n−1)∗10n+1
在取模运算中, ( a + b ) % p = ( a % p + b % p ) % p (a+b)\%p=(a\%p+b\%p)\%p (a+b)%p=(a%p+b%p)%p
此写法等价于 ( a + b ) ≡ ( a % p + b % p ) ( m o d p ) (a+b)\equiv(a\%p+b\%p) (mod\ p) (a+b)≡(a%p+b%p)(mod p)。这个写法叫做“同余”。
( a − b ) % p = ( a % p − b % p + p ) % p (a-b)\%p=(a\%p-b\%p+p)\%p (a−b)%p=(a%p−b%p+p)%p
( a ∗ b ) % p = ( a % p ∗ b % p ) % p (a*b)\%p=(a\%p*b\%p)\%p (a∗b)%p=(a%p∗b%p)%p
若 b b b与 p p p互质, a b % p = a % p ∗ b ϕ ( p ) − 1 % p \frac{a}{b}\%p=a\%p*b^{\phi(p)-1}\%p ba%p=a%p∗bϕ(p)−1%p。
b ϕ ( p ) − 1 b^{\phi(p)-1} bϕ(p)−1被称作 b b b的乘法逆元。也可以通过扩展欧几里得算法算出乘法逆元。
1 1 1~ n n n中与 n n n互质的数的个数叫做欧拉函数。记为 ϕ ( n ) \phi(n) ϕ(n)。当 n n n为质数时, ϕ ( n ) = n − 1 \phi(n)=n-1 ϕ(n)=n−1
int phi(n) {
int ans = n;
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) {
ans = ans / i * (i - 1);
while (n % i == 0) {
n /= i;
}
}
}
if (n > 1) {
ans = ans / n * (n - 1);
}
return ans;
}
或者在main函数下的第一行使用init()函数求S[n]
int S[N];
void init() {
S[0] = 0;
S[1] = 1;
int p10 = 10;
for (int i = 2; i < N; i++) {
S[i] = (S[i-1] + 1ll * i * p10 % mod) % mod;
p10 = 1ll * p10 * 10 % mod;
}
}
# include <iostream>
# include <cstdio>
# include <string>
# include <algorithm>
typedef long long ll;
const int mod = 1e9 + 7;
int inv;
int qpow(int a, int b) {
int ans = 1 % mod;
while (b) {
if (b & 1) {
ans = 1ll * ans * a % mod;
}
b >>= 1;
a = 1ll * a * a % mod;
}
return ans;
}
int qsum(int n) {
if (n == 0) {
return 0;
}
int fz = 1ll * (9 * n - 1) * qpow(10, n) % mod + 1;
fz %= mod;
return 1ll * fz * inv % mod;
}
int main() {
inv = qpow(81, mod - 2);
std::string s;
std::cin >> s;
std::reverse(s.begin(), s.end());
int n = s.size() - 1;
ll ans = 0, p10 = 1;
for (int i = 0; i < s.size(); i++) {
int d = s[i] - '0';
ll sum1 = 1ll * (n - i) * (n - i + 1) / 2 % mod * p10 % mod * d % mod;
ll sum2 = 1ll * d * qsum(i) % mod;
ans = ((ans + sum1) % mod + sum2) % mod;
p10 = p10 * 10 % mod;
}
std::cout << ans << "\n";
return 0;
}