AtCoder Beginner Contest 147
AtCoder Beginner Contest 147
https://atcoder.jp/contests/abc147
C - HonestOrUnkind2
推理题,爆搜。好那好难,一直不知道怎么下手
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int ans, n, a[N][N], b[N][N], tmp[N]; //
int check () {
for (int i = 1; i <= n; i++) {
if (!tmp[i]) continue; //只check说真话的的人
for (int j = 1; j <= n; j++) {
if (tmp[a[i][j]] != b[i][j]) return 0;
}
}
int cnt = 0;
for (int i = 1; i <= n; i++) cnt += (tmp[i] == 1);
return cnt;
}
void dfs (int x) {
if (x > n) {
ans = max (ans, check ());
return ;
}
tmp[x] = 0, dfs (x + 1);
tmp[x] = 1, dfs (x + 1);
}
int main () {
cin >> n;
memset (a, -1, sizeof a);
for (int i = 1; i <= n; i++) {
int m;
cin >> m;
for (int j = 1; j <= m; j++) cin >> a[i][j] >> b[i][j];
}
dfs (1);
cout << ans;
}
D - Xor Sum 4
有两种方法,直接 or 间接,前面疯狂WA主要是因为乘法没有随时取模导致爆longlong了
间接法
也是我最初的想法
根据a^b = a+b - 2(a&b)
得,所求为:
而 是比较好求的,只需按位 到 中统计当前位 为 的数有多少个,记个数为 ,对总和的贡献就是:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 5, mod = 1e9 + 7;
ll a[N], sum;
int n;
int main () {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], (sum += a[i]) %= mod;
(sum *= (n - 1)) %= mod;
for (int j = 59; j >= 0; j--) {
int cnt = 0;
for (int i = 1; i <= n; i++) {
if ((a[i] >> j & 1)) cnt ++;
}
if (!cnt) continue;
ll dx = (1ll << j) % mod * cnt % mod * (cnt - 1) % mod;
// cout << dx << ' ';
sum = (sum - dx + mod) % mod;
}
cout << sum << endl;
}
//a^b = a+b - 2(a&b)
//a&b就是看有多少位是全1
直接法
直接求异或和。
按位 到 中统计当前位 为 的数有多少个,记个数为 ,对总和的贡献就是
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 5, mod = 1e9 + 7;
ll a[N], sum;
int n;
int main () {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int j = 60; j >= 0; j--) {
ll cnt = 0;
for (int i = 1; i <= n; i++) {
if (a[i] >> j & 1) cnt ++;
}
if (!cnt) continue;
sum = (sum + (1ll << j) % mod * cnt % mod * (n - cnt) % mod) % mod;
// cout << cnt << ' ';
}
cout << sum;
}
//a^b = a+b - 2(a&b)
//a&b就是看有多少位是全1
E - Balanced Path
简单线性dp。
然鹅我又想错柿子了,状态总是设不对,就算设对了,也不会转移,就算侥幸转移对了,边界也没设置好呜呜呜呜呜
这题主要是学一个Bitset优化小技巧。
朴素版:
#include <bits/stdc++.h>
using namespace std;
const int N = 85, M = (80 + 80) * 80;
bool f[N][N][M * 2 + 5]; //f[i][j][k]: 差值为 k 的可行性
int n, m, a[N][N], b[N][N];
bool Range (int x, int y) {
if (x < 1 || x > n || y < 1 || y > m) return false;
return true;
}
int main () {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> b[i][j];
a[i][j] -= b[i][j];
}
}
//f[0][0][0] = 1;
f[1][1][a[1][1] + M] = f[1][1][M - a[1][1]] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
for (int k = -M; k <= M; k++) {
f[i][j][k+M] |= (f[i-1][j][k-a[i][j]+M] | f[i-1][j][k+a[i][j]+M]);
f[i][j][k+M] |= (f[i][j-1][k-a[i][j]+M] | f[i][j-1][k+a[i][j]+M]);
}
}
}
for (int i = 0; i <= M; i++) {
if (f[n][m][i + M] || f[n][m][M - i]) {
cout << i << endl;
break;
}
}
}
//差值范围:[-M, M]
//暂定为 red-blue
//很不爽,但是还是要交
bitset优化版:
#include <bits/stdc++.h>
using namespace std;
const int N = 85, M = (80 + 80) * 80;
bitset<2*M+5>f[N][N];
int n, m, a[N][N], b[N][N];
int main () {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> b[i][j];
a[i][j] -= b[i][j];
}
}
//f[0][0][0] = 1;
f[1][1][a[1][1] + M] = f[1][1][M - a[1][1]] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int k = abs (a[i][j]);
f[i][j] |= ((f[i-1][j] << k) | (f[i-1][j] >> k));
f[i][j] |= ((f[i][j-1] << k) | (f[i][j-1] >> k));
}
}
for (int i = 0; i <= M; i++) {
if (f[n][m][i + M] || f[n][m][M - i]) {
cout << i << endl;
break;
}
}
}
//差值范围:[-M, M]
//暂定为 red-blue
//bitset优化
F - Sum Difference
蛮有意思的一道数论小题。
题意
给你一个首项为 ,公差为 的 项等差序列,从中任选 个数放入集合 ,剩余数放入集合 ,问 从 到 , 共有多少种取值可能。
分析
先进行一个转化:, 其中 为所有数之和。 是固定的,所求转化为 有多少种不同的取值。
首先特判掉 的情况,即所有数都等于 ,故 ,输出 ;否则输出
可以把他对称过来,即 分别取反之后转化为 的情况。
考虑 ,则有 , 为选取的 个数的 的系数之和。
则 (取前 个,后 个)
即
时对应的 的图像形如下图,是一个个一次函数:
函数上的点对应在 轴上的值就是 ,所求即为每个一次函数在定义域为 范围内的所有取值。观察上图,发现条直线上的点的加纵坐标间距是固定的,如蓝色那条线上,对应 轴上的所有点的距离都是 ,绿线相邻点之间的距离是 ,红线是 ,都是 的倍数。那么每条线投影在 轴上的点坐标都是间隔固定距离的点集,因为不同线之间的点集可能会重合,所以我们考虑如何去重。
这里就涉及到了本题最精华的部分,将离散的点转化为连续的线段。因距离和 有关,不妨令 , 是连续的,分布在 范围内的数,即长度为 的线段,问题得以解决。可以理解为,此时 转化为 模 意义下的一段线段。我们把他计入对应集合,进行线段合并,总和就是最终答案。(依据 划分,合并线段集合,可以把它理解为"基")
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair<ll, ll> pii;
const int N = 2e5 + 5;
ll n, x, d, sum;
map<int, vector<pii>> v;
int main () {
cin >> n >> x >> d;
if (d == 0) {
if (x == 0) cout << 1; //0
else cout << n + 1;
return 0;
}
if (d < 0) x = -x, d = -d;
for (ll k = 0; k <= n; k++) { //别忘了k=0!!!
ll L = k * (k - 1) / 2, R = k * (2 * n - k - 1) / 2;
ll t = k * x; //离散的点连续化
L += t / d, R += t / d;
v[t % d].push_back ({L, R}); //%d意义下同运算
}
for (auto vi : v) { //区间合并
ll t = vi.first;
auto tt = vi.second;
sort (tt.begin (), tt.end ());
ll l = tt[0].first, r = tt[0].second;
for (int i = 1; i < tt.size (); i++) {
if (tt[i].first <= r) r = max (r, tt[i].second); //区间有重叠,合并为大区间
else sum += r - l + 1, l = tt[i].first, r = tt[i].second;
}
sum += r - l + 1;
}
cout << sum << endl;
}
//s=kx+vd