The 15th Heilongjiang Provincial Collegiate Programming Contest--队内训练

题目 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(xuxv)(yvyu)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=23237
  • 由上述规律看出来,每次有值的答案都选了最大的两个因子;并且由博弈来看,挑出两个因子的数只能进行两次操作,貌似这个猜测是正确的,然后瞎搞了一番,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;
}


猜你喜欢

转载自blog.csdn.net/bloom_er/article/details/110505585