题目 | A | B | C | D | E | F | G | H | I | J | K | L |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Solved | ♠ \spadesuit ♠ | ∙ \bullet ∙ | ♠ \spadesuit ♠ | ♠ \spadesuit ♠ | ∙ \bullet ∙ | ♠ \spadesuit ♠ | ∙ \bullet ∙ | ♠ \spadesuit ♠ |
♠ \spadesuit ♠比赛时通过; ∙ \bullet ∙:赛后通过; ⊘ \oslash ⊘ :比赛时尝试了未通过; ∘ \circ ∘:比赛时未尝试
A. August
开场签到题,题意就是求题目中的图形面积,一眼看下去,直觉爱心的下半部分可以构成一个长方形。试了下答案对了。瞎搞才是硬道理
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 2e3 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = acos(-1);
int main() {
#ifdef _DEBUG
// FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
int T;
rd(T);
while (T--) {
ll a, b; rd(a), rd(b);
ll res1 = a * b * 4;
ll res2 = a * a;
double sum = res1 + pi * res2;
printf("%.8f\n", sum);
}
return 0;
}
B. Bills of Paradise
这题一看就是一道线段树,但是训练的时候太困了实在没有复杂度低的想法。之后看了博客才恍然大悟,只要结合并查集就可以大大降低这题的复杂度(反正训练的时候就是想不到的呜呜)。
题意比较复杂,下面慢慢解释。
题目给定一个函数,用其产生 n n n个数,存入数组 a a a。
接下来给出 q q q个询问。
- 询问 F F F,输入一个数 x x x,输出数组 a a a中大于等于 x x x的第一个未被标记的数,若没有则输出 1000000000000 1000000000000 1000000000000。
- 询问 D D D,输入一个数 x x x,标记数组 a a a中大于等于 x x x的第一个未被标记的数,若没有则跳过这步操作。
- 询问 C C C,输入一个数 x x x,查询数组 a a a中小于等于 x x x的所有未标记数之和,若没有则输出0。
- 询问 R R R,输入一个数 x x x,清楚数组 a a a中小于等于 x x x的数的所有标记,若没有标记过,则不操作。(题目中提到这个操作总共只会进行10次)
这里是一些前置操作
- 用题目中给的函数进行 a a a的生成后,将其 s o r t sort sort从小到大排序。
- 维护数组 a a a的前缀和。
- 初始化并查集。并查集维护这个数是否被标记过,若没有被标记过其祖先就是自己本身。
- 接下来进行建树,树的初始值为 0 0 0,因此不需要 p u s h u p pushup pushup的操作,都赋值为0即可。树中保存的是标记的过的数的值之和,因此没被标记过的数在线段树中的权值也就自然是 0 0 0了。
接下来是四种询问的具体细分
- 询问 F F F,先使用 l o w e r _ b o u n d lower\_bound lower_bound二分查找到大于等于 x x x的数,然后通过并查集查找其祖先,也就是没有被标记过的哪个数。若找到则直接输出这个数,没有找到就输出 1000000000000 1000000000000 1000000000000。没有找到共有两种情况:第一种情况是就找不到一个数是大于等于 x x x的,也就是 l o w e r _ b o u n d lower\_bound lower_bound出来的下标等于 n + 1 n+1 n+1(具体怎么 l o w e r _ b o u n d lower\_bound lower_bound这一点在代码中有体现);第二种情况是大于等于 x x x的数每个都被标记过了,也就是他们的祖先是 n + 1 n+1 n+1。
- 询问 D D D,同样使用 l o w e r _ b o u n d lower\_bound lower_bound二分查找到大于等于 x x x的数,若数组 a a a中能找到这样的数则继续进行操作。若其本身就没有被标记过,则直接标记其本身(线段树中进行 u p d a t e update update),并将其祖先设置为他的后一个数。若其本身被标记过并且其祖先是 n + 1 n+1 n+1,说明找不到没有标记过的数了,则跳过此次操作。如果其祖先不是 n + 1 n+1 n+1,则将其祖先进行标记(线段树中进行 u p d a t e update update),并且将其祖先的祖先设置为其祖先的后面一个数。
- 询问 C C C,先使用 u p p e r _ b o u n d upper\_bound upper_bound二分查找到小于等于 x x x的数,若找不到,也就是找到的则输出0。找到了就将算好的前缀和减去前面被标记过的数之和(使用线段树的 q u e r y query query求和)就可以得到答案输出了。
- 询问 R R R,同样使用 u p p e r _ b o u n d upper\_bound upper_bound二分查找到小于等于 x x x的数,若找不到,就不操作了。找到了就 u p d a t e update update清空区间内的所有值并将这个区间的祖先都还原为本身即可。
具体细节看代码,记得要开 l o n g l o n g longlong longlong
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>
#define lowbit(x) ((x) & -(x))
#define endl "\n"
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e6 + 10, NN = 2e3 + 10, INF = 0x3f3f3f3f, LEN = 20;
const ll MOD = 1e9 + 7;
const ull seed = 31;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int n;
int fa[N];
ll a[N], sum[N], tree[N << 2], ERROR;
ull k1, k2;
ull xorShift128Plus() {
ull k3 = k1, k4 = k2;
k1 = k4;
k3 ^= k3 << 23;
k2 = k3 ^ k4 ^ (k3 >> 17) ^ (k4 >> 26);
return k2 + k4;
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
int fx = find(x), fy = find(y);
fa[fx] = fy;
}
void gen() {
scanf("%d %llu %llu", &n, &k1, &k2);
for (int i = 1; i <= n; i++)
a[i] = xorShift128Plus() % 999999999999 + 1;
}
inline void pushup(int rt) {
tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
}
void buildtree(int rt, int l, int r) {
tree[rt] = 0;
if (l == r)
return;
int mid = (l + r) >> 1;
buildtree(rt << 1, l, mid);
buildtree(rt << 1 | 1, mid + 1, r);
}
void update(int rt, int l, int r, int id, ll val) {
if (l == r && l == id) {
tree[rt] += val;
return;
}
int mid = (l + r) >> 1;
if (id <= mid)
update(rt << 1, l, mid, id, val);
else
update(rt << 1 | 1, mid + 1, r, id, val);
pushup(rt);
}
void update_clear(int rt, int l, int r, int ql, int qr) {
if (l == r) {
tree[rt] = 0;
return;
}
int mid = (l + r) >> 1;
if (ql <= mid)
update_clear(rt << 1, l, mid, ql, qr);
if (qr > mid)
update_clear(rt << 1 | 1, mid + 1, r, ql, qr);
pushup(rt);
}
ll query(int rt, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr)
return tree[rt];
int mid = (l + r) >> 1;
ll ans = 0;
if (ql <= mid)
ans += query(rt << 1, l, mid, ql, qr);
if (qr > mid)
ans += query(rt << 1 | 1, mid + 1, r, ql, qr);
return ans;
}
void init() {
ERROR = 1e12;
sum[0] = 0;
for (int i = 1; i <= n; i++) {
fa[i] = i;
sum[i] = sum[i - 1] + a[i];
}
fa[n + 1] = n + 1;
}
int main() {
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
gen();
sort(a + 1, a + 1 + n);
init();
buildtree(1, 1, n);
int q;
scanf("%d", &q);
while (q--) {
char op;
ll x;
scanf(" %c%lld", &op, &x);
if (op == 'F') {
int id = lower_bound(a + 1, a + 1 + n, x) - a;
if (id == n + 1) {
printf("%lld\n", ERROR);
continue;
}
int pos = find(id);
if (pos == n + 1)
printf("%lld\n", ERROR);
else
printf("%lld\n", a[pos]);
} else if (op == 'D') {
int id = lower_bound(a + 1, a + 1 + n, x) - a;
if (id == n + 1)
continue;
if (id == find(id)) {
// 未被标记
update(1, 1, n, id, a[id]);
merge(id, id + 1);
} else {
int pos = find(id + 1);
if (pos == n + 1)
continue;
update(1, 1, n, pos, a[pos]);
merge(pos, pos + 1);
}
} else if (op == 'C') {
int id = upper_bound(a + 1, a + 1 + n, x) - a - 1;
if (id == 0) {
printf("0\n");
continue;
}
ll ans = query(1, 1, n, 1, id);
ll temp = sum[id];
printf("%lld\n", temp - ans);
} else if (op == 'R') {
int id = upper_bound(a + 1, a + 1 + n, x) - a - 1;
if (id == 0)
continue;
update_clear(1, 1, n, 1, id);
for (int i = 1; i <= id; i++)
fa[i] = i;
}
}
return 0;
}
C. Cornelia Street
题意就是找出两个相同长度的字符串 A A A 和 B B B ,满足 AAABBBAAa能够满足题目给出的字符串,其中 a a a 是 A A A 的前缀。
- 一开始被这道题的数据范围给吓到了,但还是去试了一下看能不能过,就是枚举长度一个一个比较过去,没想到…还真的AC了,有惊有险
- 枚举长度 l l l ,后面在比较串的时候一个一个比较过去就可以了,基本的操作。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 8e5 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
char s[N];
int pos, len, now;
bool check1(int l) {
now = (len / 2) / l * l;
bool flag = false;
for (int i = l; i < len; ++i) {
if (s[i % l] == s[i]) {
flag = true;
}
else {
now = i / l * l;
return true;
}
}
return true;
}
bool check2(int l) {
int nxt = len;
bool flag = false;
for (int i = now+l; i < len; ++i) {
if (s[i % l + now] == s[i]) {
flag = true;
}
else {
int pos = i / l * l;
for (int j = pos; j < len; ++j) {
if (s[j % l] != s[j]) {
return false;
}
}
return true;
}
}
return true;
}
int main() {
#ifdef _DEBUG
// FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
// int T;
// rd(T);
// while (T--) {
scanf(" %s", s);
len = strlen(s);
for (int i = 1; i <= len / 2; ++i) {
if (check1(i) && check2(i)) {
for (int j = 0; j < i; ++j) printf("%c", s[j]); printf(" ");
for (int j = 0; j < i; ++j) printf("%c", s[j + now]);
puts("");
break;
}
}
// }
return 0;
}
F. False God
- 根据题目意思,每个步兵都会向下移动一格,但值得注意的是,这些步兵的相对位置并不会发生改变。
- 利用相对位置不变的特性,可以把一个步兵和另外的步兵建立一个单向边,表示若金在步兵 u u u 的位置时,金可以到步兵 v v v 的位置并把步兵 v v v 吃掉
- 而金在一个位置能够吃到步兵 v v v 的条件是 a b s ( x u − x v ) − ( y v − y u ) ≤ 1 abs(x_u-x_v)-(y_v-y_u)≤1 abs(xu−xv)−(yv−yu)≤1, 这个可以根据金的移动规律推得。
- 在知道以上情况后,直接暴力 O ( n 2 ) O(n^2) O(n2) 对所有点进行建立单向边即可。随后跑一遍 d f s dfs dfs 得出最大深度即为答案。
上面的思路被 hack 了,暂未更新。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 1e3 + 10;
const int M = 1e7 + 10;
const ll MAX = 1e12;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
int head[N], cntE;
struct edge {
int next, to;
}e[M];
void add(int u, int v) {
e[cntE].to = v;
e[cntE].next = head[u];
head[u] = cntE++;
}
int n;
ll x[N], y[N];
ll a[N];
bool vis[N];
ll dfs(int x) {
vis[x] = true;
for (int i = head[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (vis[v]) {
a[x] = max(a[x], a[v] + 1);
continue;
}
a[x] = max(a[x], dfs(v) + 1);
}
return a[x];
}
int main() {
#ifdef _DEBUG
// FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
int T; rd(T);
while (T--) {
rd(x[0]); rd(y[0]);
rd(n);
for (int i = 1; i <= n; ++i) {
rd(x[i]); rd(y[i]);
}
memset(head, -1, sizeof(int)*(n+10)); cntE = 0;
for (int i = 1; i <= n; ++i) {
if (abs(x[0] - x[i]) - (y[i] - y[0]) <= 1) {
add(0, i);
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (i == j) continue;
if (abs(x[i] - x[j]) - (y[j] - y[i]) <= 1) {
add(i, j);
}
}
}
for (int i = 1; i <= n; ++i) a[i] = 1;
memset(vis, false, sizeof(bool) * (n + 10));
a[0] = 0;
printf("%lld\n", dfs(0)-1);
}
return 0;
}
G. Goodbye
老实说,这题…emmm…题目三个人都没读懂什么意思,后来我强行去看答案找规律发现
*
- 4 = 2 2 , 8 = 2 3 , 666 = 2 ∗ 3 2 ∗ 37 4=2^2,8=2^3,666=2*3^2*37 4=22,8=23,666=2∗32∗37
- 由上述规律看出来,每次有值的答案都选了最大的两个因子;并且由博弈来看,挑出两个因子的数只能进行两次操作,貌似这个猜测是正确的,然后瞎搞了一番,AC…
瞎搞才是硬道理
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
int prime[N]; bool isprime[N] = {
false };
int cnt;
void init() {
cnt = 0;
isprime[0] = isprime[1] = true;
for (int i = 2; i <= N - 5; ++i) {
if (!isprime[i]) {
prime[++cnt] = i;
}
for (int j = 1; j <= cnt && prime[j] * i <= (N - 10); ++j) {
isprime[prime[j] * i] = true;
if (i % prime[j] == 0) break;
}
}
}
int a[N];
vector<pii>vec;
ll fp(ll x, ll y) {
ll ans = 1;
while (y) {
if (y & 1) ans *= x;
x *= x;
y >>= 1;
}
return ans;
}
bool check() {
bool flag = false;
int cnt = 0;
for (auto v : vec) {
cnt += v.second;
}
if (cnt < 3) return false;
int ans = vec[vec.size() - 1].first;
if (vec[vec.size() - 1].second > 1) ans *= vec[vec.size() - 1].first;
else ans *= vec[vec.size() - 2].first;
printf("%d\n", ans);
return true;
}
int main() {
#ifdef _DEBUG
// FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
init();
int T;
rd(T);
while (T--) {
int n; rd(n);
vec.clear();
if (n == 1) {
puts("0");
continue;
}
if (!isprime[n]) {
puts("0");
continue;
}
for (int i = 1; i <= cnt && prime[i] * prime[i] <= n; ++i) {
if (n % prime[i] == 0) {
int tot = 0;
while (n % prime[i] == 0) ++tot, n /= prime[i];
vec.push_back({
prime[i],tot });
}
}
if (n > 1) vec.push_back({
n,1 });
if (!check()) puts("-1");
}
return 0;
}