2020 Petrozavodsk Winter Camp, Jagiellonian U Contest 部分题解
2020 Petrozavodsk Winter Camp, Jagiellonian U Contest
B. Binomial
题意:给定一个数组 \(a_n\) ,求有几对数字 \((a_i,a_j)\) 满足 \(C^{a_i}_{a_j}\) 为奇数。
分析:\(C^m_n\) 为奇数的条件是 n & m = m
,这就转化成一个经典问题,参考博客:https://codeforces.com/blog/entry/45223
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 5;
int p[maxn];
LL a[1 << 20];
int main() {
int t;
scanf("%d", &t);
while (t--) {
memset(a, 0, sizeof(a));
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &p[i]);
a[p[i]]++;
}
for (int i = 0; i < 20; i++)
for (int j = 0; j < maxn; j++)
if (j & (1 << i))
a[j] += a[j ^ (1 << i)];
LL ans = 0;
for (int i = 1; i <= n; i++)
ans += a[p[i]];
printf("%lld\n", ans);
}
return 0;
}
E. Contamination
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e6 + 5;
struct Node {
int type;
int h;
int p;
int y_max;
int x_min;
int x_max;
} node[maxn];
typedef pair<int, int> pii;
pii c[maxn];
namespace Segment {
const int inf = -(2e9) - 100;
int tree[maxn << 2];
void build(int root, int left, int right)
{
tree[root] = inf;
if (left == right)
return;
int mid = (left + right) >> 1;
build(root << 1, left, mid);
build(root << 1 | 1, mid + 1, right);
}
void modify(int root, int left, int right, int x, int val)
{
if (left == right) {
tree[root] = max(tree[root], val);
return;
}
int mid = (left + right) >> 1;
if (x <= mid)
modify(root << 1, left, mid, x, val);
else
modify(root << 1 | 1, mid + 1, right, x, val);
tree[root] = max(tree[root << 1], tree[root << 1 | 1]);
}
int query(int root, int left, int right, int stdl, int stdr)
{
if (left >= stdl && right <= stdr)
return tree[root];
int mid = (left + right) >> 1;
int res = inf;
if (stdl <= mid)
res = max(res, query(root << 1, left, mid, stdl, stdr));
if (stdr > mid)
res = max(res, query(root << 1 | 1, mid + 1, right, stdl, stdr));
return res;
}
}
bool res[maxn];
int main() {
int n, q;
scanf("%d%d", &n, &q);
int k = 0, x, y, r;
for (int i = 1; i <= n; i++) {
scanf("%d%d%d", &x, &y, &r);
node[++k].type = 1;
node[k].h = y - r;
node[k].p = x;
node[k].y_max = y + r;
node[++k].type = 2;
node[k].h = y + r;
node[k].p = x;
node[k].y_max = y + r;
}
int p_x, p_y, q_x, q_y, y_min, y_max;
for (int i = 1; i <= q; i++) {
node[++k].type = 3;
scanf("%d%d%d%d%d%d", &p_x, &p_y, &q_x, &q_y, &y_min, &y_max);
node[k].p = i;
if (p_x > q_x)
swap(p_x, q_x);
node[k].x_min = p_x;
node[k].x_max = q_x;
node[k].h = y_min;
node[k].y_max = y_max;
}
int cnt = 0;
for (int i = 1; i <= 2 * n; i++)
c[++cnt] = make_pair(node[i].p, i);
for (int i = 2 * n + 1; i <= k; i++) {
c[++cnt] = make_pair(node[i].x_min, i << 1);
c[++cnt] = make_pair(node[i].x_max, (i << 1) | 1);
}
sort(c + 1, c + 1 + cnt);
int rh = 0;
for (int i = 1; i <= cnt;) {
int rp = i;
rh++;
while (++i <= cnt && c[i].first == c[i - 1].first)
;
for (int j = rp; j < i; j++) {
if (c[j].second <= 2 * n)
node[c[j].second].p = rh;
else {
if (c[j].second & 1)
node[c[j].second >> 1].x_max = rh;
else
node[c[j].second >> 1].x_min = rh;
}
}
}
sort(node + 1, node + 1 + k, [&](const Node& a, const Node& b) {
if (a.h == b.h)
return a.type < b.type;
return a.h < b.h;
});
Segment::build(1, 1, rh);
for (int i = 1; i <= k; i++) {
if (node[i].type == 1) {
Segment::modify(1, 1, rh, node[i].p, node[i].y_max);
}
else if (node[i].type == 3) {
if (Segment::query(1, 1, rh, node[i].x_min, node[i].x_max) < node[i].y_max)
res[node[i].p] = true;
else
res[node[i].p] = false;
}
}
for (int i = 1; i <= q; i++) {
if (res[i])
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
G. Invited Speakers
题意:给定平面上两个大小为 \(n\) 的点集,要求给出一种构造方案,使得这两个点集中的点两两匹配(两个点之间画出一条折线段将它们连接称为匹配),并且不存在任意两对折线段有交点。(给定的点集不存在三点共线,不存在任意两点的横坐标或纵坐标相同)
分析:如下图,先将点集按照水平序排序,然后画矩形绕圈。
#include <bits/stdc++.h>
using namespace std;
const int add = 500;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int t; cin >> t;
while (t--) {
int n; cin >> n;
vector<pair<int, int> > spot(n), table(n);
for (auto& i : spot) cin >> i.first >> i.second;
for (auto& i : table) cin >> i.first >> i.second;
sort(spot.begin(), spot.end());
sort(table.begin(), table.end());
for (int i = 0; i < n; ++i) {
cout << "6\n";
cout << spot[i].first << ' ' << spot[i].second << '\n';
cout << spot[i].first << ' ' << i + add << '\n';
cout << -i - add << ' ' << i + add << '\n';
cout << -i - add << ' ' << -i - add << '\n';
cout << table[i].first << ' ' << -i - add << '\n';
cout << table[i].first << ' ' << table[i].second << '\n';
}
}
}
H. Lighthouses
#include <bits/stdc++.h>
using namespace std;
const int maxn = 305 * 2;
bool a[maxn][maxn];
long long x[maxn], y[maxn];
double dist(int a, int b) {
return sqrt((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]));
}
double pre[maxn][maxn], suf[maxn][maxn];
int main() {
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
for (int i = 1; i <= 2 * n; i++)
for (int j = 1; j <= 2 * n; j++)
a[i][j] = false,
pre[i][j] = suf[i][j] = 0;
for (int i = 1; i <= n; i++)
scanf("%lld%lld", &x[i], &y[i]);
for (int i = n + 1; i <= (n << 1); i++)
x[i] = x[i - n], y[i] = y[i - n];
int u, v, m;
scanf("%d", &m);
while (m--) {
scanf("%d%d", &u, &v);
a[u][v] = a[v][u] = true;
u += n;
a[u][v] = a[v][u] = true;
v += n;
a[u][v] = a[v][u] = true;
u -= n;
a[u][v] = a[v][u] = true;
}
double ans = 0;
for (int len = 1; len <= n; len++) {
for (int l = 1, r = l + len - 1; r <= (n << 1); l++, r++) {
ans = max(ans, pre[l][r]);
ans = max(ans, suf[l][r]);
for (int i = max(1, r - n + 1); i < l; i++) {
if (a[i][l])
pre[i][r] = max(pre[i][r], pre[l][r] + dist(i, l));
if (a[i][r])
pre[i][r] = max(pre[i][r], suf[l][r] + dist(i, r));
}
for (int i = r + 1; i <= min((n << 1), l + n - 1); i++) {
if (a[r][i])
suf[l][i] = max(suf[l][i], suf[l][r] + dist(r, i));
if (a[l][i])
suf[l][i] = max(suf[l][i], pre[l][r] + dist(l, i));
}
}
}
printf("%.10lf\n", ans);
}
return 0;
}
I. Sum of Palindromes
题意:将一个大数拆成最多 \(25\) 个回文数。
分析:我们每次将问题规模折半,比如 \(123407897\) 就能够减去 \(123404321\) ;如果不能折半就找到一位退位,比如 \(123401234\) 可以减去 \(123393321\) (本来减去 \(123404321\) ,但是不行,因此退位)。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int t; cin >> t;
while (t--) {
string s; cin >> s;
vector<int> a(s.length());
vector<vector<int> > ans;
for (int i = 0; i < s.length(); ++i) a[i] = (s[i] - '0');
reverse(a.begin(), a.end());
while (!a.empty()) {
int len = a.size();
if (a.back() == 1) {
int sum = -1;
for (auto i : a) sum += i;
if (!sum) {
if (len > 1) ans.emplace_back(len - 1, 9);
ans.emplace_back(1, 1);
break;
}
}
vector<int> tmp = a;
for (int i = 0; i < (len >> 1); ++i) tmp[i] = tmp[len - i - 1];
vector<int> pa = a, pb = tmp;
reverse(pa.begin(), pa.end());
reverse(pb.begin(), pb.end());
if (pa < pb) {
int pos = (len >> 1);
while (!tmp[pos]) tmp[pos++] = 9;
--tmp[pos];
for (int i = 0; i < (len >> 1); ++i) tmp[i] = tmp[len - i - 1];
}
ans.emplace_back(tmp);
for (int i = 0; i < len; ++i) a[i] -= tmp[i];
for (int i = 0; i < len; ++i)
if (a[i] < 0)
a[i] += 10, --a[i + 1];
while (!a.empty() && a.back() == 0) a.pop_back();
}
cout << ans.size() << '\n';
for (auto i : ans) {
for (auto j : i) cout << j;
cout << '\n';
}
}
}
J. Space Gophers
题意:在一个巨大的三维网格空间中建造 \(n\) 条垂直坐标系的隧道,\(q\) 个询问,询问两点之间是否可以通过隧道到达。
分析:考虑使用并查集维护,但是如何合并两条隧道较难处理,因为这个网格空间的量级是 \(1e6\) 的,我们不可能每次都将整条隧道中的点合并。假设建了一条 \((x,y,-1)\) 的隧道,如下图加粗蓝色柱体:
我们思考,如果建了这条 \((x,y,-1)\) 的隧道,有哪些隧道应该与之合并,显然 \(x,y,z\) 三个方向的隧道都应该被考虑到,如上图:
- \(z\) 方向的四条相邻隧道(图中淡绿色透明隧道为这四条隧道之一)显然需要合并:\((x-1,y,-1),\ (x,y-1,-1),\ (x+1,y,-1),\ (x,y+1,-1)\) ;
- \(y\) 方向上就比较麻烦(图中透明深绿色),因为只要满足 \(x'\in \{x-1,x,x+1\}\) 的隧道 \((x',-1,z')\) 都应该与之合并;
- \(x\) 方向上同理(图中透明黄色),只要满足 \(y'\in \{y-1,y,y+1\}\) 的隧道 \((-1,y',z')\) 都应该与之合并。
实际上,我们发现对于这三个方向的隧道,我们可以分两类讨论:一类是需要合并的两个隧道方向一致的,如上方的情况 \(1\) ;另一类是需要合并的两个隧道方向不一致的,如上方的情况 \(2,3\) 。第一类情况很容易处理,我们直接用 \(std::map\) 存储三元集 \((x,y,-1)\) ,然后将 \((x-1,y,-1),\ (x,y-1,-1),\ (x+1,y,-1),\ (x,y+1,-1)\) 与其合并即可;第二类情况相对麻烦,我们考虑降维操作:将 \((x,y,-1)\) 的隧道分别投影到面 \(zOy,zOx\) ,然后分别用 \(y,x\) 坐标来指代这条隧道,然后我们就能够将 \(y'\in\{y-1,y,y+1\},\ x'\in\{x-1,x,x+1\}\) 的隧道合并了。
然后就写了个 TLE ,还要剪一下枝。。。
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 1000010
using namespace std;
struct Triple {
int x, y, z;
Triple() {}
Triple(int x_, int y_, int z_) : x(x_), y(y_), z(z_) {}
bool operator < (const Triple& rhs) const {
if (x != rhs.x) return x < rhs.x;
return y == rhs.y ? z < rhs.z : y < rhs.y;
}
};
struct DisjointSetUnion { //并查集
int pa[SIZE];
void init(int n) {
for (int i = 0; i <= n; ++i) pa[i] = i;
}
int find(int x) {
return pa[x] == x ? x : pa[x] = find(pa[x]);
}
bool isSame(int x, int y) { return find(x) == find(y); }
int union_vertices(int x, int y) {
int xr = find(x), yr = find(y);
if (xr != yr) {
pa[yr] = xr;
return 1;
}
return 0;
}
} U;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int t; cin >> t;
while (t--) {
int n; cin >> n;
U.init(1000000);
vector<set<int> > xy(SIZE), yz(SIZE), zx(SIZE), yx(SIZE), zy(SIZE), xz(SIZE);
map<Triple, int> MP;
for (int i = 0; i < n; ++i) {
int x, y, z;
cin >> x >> y >> z;
if (x == -1) {
yz[y].insert(i);
zy[z].insert(i);
}
else if (y == -1) {
xz[x].insert(i);
zx[z].insert(i);
}
else {
xy[x].insert(i);
yx[y].insert(i);
}
MP[Triple(x, y, z)] = i;
}
for (auto it : MP) {
Triple tp = it.first;
int id = it.second;
if (tp.x == -1) {
for (auto y : { tp.y - 1, tp.y, tp.y + 1 }) {
for (auto i : yx[y]) {
U.union_vertices(i, id);
}
if (!yx[y].empty()) yx[y] = { *yx[y].begin() };
if (MP.count(Triple(-1, y, tp.z)))
U.union_vertices(MP[Triple(-1, y, tp.z)], id);
}
for (auto z : { tp.z - 1, tp.z, tp.z + 1 }) {
for (auto i : zx[z]) {
U.union_vertices(i, id);
}
if (!zx[z].empty()) zx[z] = { *zx[z].begin() };
if (MP.count(Triple(-1, tp.y, z)))
U.union_vertices(MP[Triple(-1, tp.y, z)], id);
}
}
else if (tp.y == -1) {
for (auto x : { tp.x - 1, tp.x, tp.x + 1 }) {
for (auto i : xy[x]) {
U.union_vertices(i, id);
}
if (!xy[x].empty()) xy[x] = { *xy[x].begin() };
if (MP.count(Triple(x, -1, tp.z)))
U.union_vertices(MP[Triple(x, -1, tp.z)], id);
}
for (auto z : { tp.z - 1, tp.z, tp.z + 1 }) {
for (auto i : zy[z]) {
U.union_vertices(i, id);
}
if (!zy[z].empty()) zy[z] = { *zy[z].begin() };
if (MP.count(Triple(tp.x, -1, z)))
U.union_vertices(MP[Triple(tp.x, -1, z)], id);
}
}
else {
for (auto x : { tp.x - 1, tp.x, tp.x + 1 }) {
for (auto i : xz[x]) {
U.union_vertices(i, id);
}
if (!xz[x].empty()) xz[x] = { *xz[x].begin() };
if (MP.count(Triple(x, tp.y, -1)))
U.union_vertices(MP[Triple(x, tp.y, -1)], id);
}
for (auto y : { tp.y - 1, tp.y, tp.y + 1 }) {
for (auto i : yz[y]) {
U.union_vertices(i, id);
}
if (!yz[y].empty()) yz[y] = { *yz[y].begin() };
if (MP.count(Triple(tp.x, y, -1)))
U.union_vertices(MP[Triple(tp.x, y, -1)], id);
}
}
}
auto getId = [&](Triple p) {
int x = p.x, y = p.y, z = p.z;
int id = -1;
if (MP.count(Triple(x, y, -1)))
id = MP[Triple(x, y, -1)];
if (MP.count(Triple(x, -1, z)))
id = MP[Triple(x, -1, z)];
if (MP.count(Triple(-1, y, z)))
id = MP[Triple(-1, y, z)];
return id;
};
int q; cin >> q;
while (q--) {
Triple a, b;
cin >> a.x >> a.y >> a.z >> b.x >> b.y >> b.z;
if (U.isSame(getId(a), getId(b))) cout << "YES\n";
else cout <<"NO\n";
}
}
}
L. Wizards Unite
#include<bits/stdc++.h>
#define ll long long
#define maxn 100100
using namespace std;
ll a[maxn];
int main() {
int t;
scanf("%d", &t);
while (t--) {
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
sort(a, a + n);
ll ans = 0;
for (int i = 0; i < n - k; i++) ans += a[i];
for (int i = n - k; i < n; i++) ans = max(ans, a[i]);
printf("%lld\n", ans);
}
return 0;
}