2019中国大学生程序设计竞赛-女生专场

Contest Info


[Practice Link](https://cn.vjudge.net/contest/311102)
Solved A B C D E F G H I J K
10/11 O O O O Ø Ø O Ø - Ø Ø
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


A. Ticket

签到题。

#include <bits/stdc++.h>
using namespace std;

#define db double
int n, a;
db res;
const db eps = 1e-8;

int main() {
	while (scanf("%d", &n) != EOF) {
		res = 0;
		for (int i = 1; i <= n; ++i) {
			scanf("%d", &a);
			if ((res >= 100 || fabs(res - 100) < eps) && res < 150) {
				res += a * 0.8;
			} else if ((res >= 150 || fabs(res - 150) < eps) && res < 400) {
				res += a * 0.5;
			} else {
				res += a;
			}
		}
		printf("%.2f\n", res);
	}
	return 0;
}

B. Gcd

题意:
\(1-n\)\(n\)个数分成两组,每组至少一个数,要求两组的数的和\(gcd\)最大。

思路:
考虑\(n\)个数的总和是\(sum = \frac{n(n + 1)}{2}\),那么假设\(x\;|\; sum(1 < x < sum)\),那么我们一定能拆成两组,一组是\(\frac{sum}{x}\),一组是\(sum - \frac{sum}{x}\),那么我们取最小的\(x\)即可。
那么怎么找到这个\(x\)呢?
直接暴力从小到大找就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;

#define ll long long 
ll n;

ll f(ll n) {
	return n * (n + 1) / 2;
}

int main() {
	while (scanf("%lld", &n) != EOF) {
		ll F = f(n);
		for (ll i = 2; ; ++i) {
			if (F % i == 0) {
				printf("%lld\n", F / i);
				break;
			}
		}
	}
	return 0;
}

C.Function

题意:
\(n\)个二次函数\(F_i(x) = a_ix^2 + b_ix + c_i(1 \leq i \leq n)\)
要给每个二次函数分配\(x_i\),使得\(\sum\limits_{i = 1}^n x_i = m\),并且\(\sum\limits_{i = 1}^n F_i(x_i)\)最小。

思路:
考虑\(c_i\)\(x_i\)无关,直接加入答案。
再考虑对于每个二次函数\(F_i(x_i)\),从\(F_i(x_i) \rightarrow F_i(x_i + 1)\)增加了多少?
增加的贡献是\(2a_ix_i + a_i + b_i\),并且由于\(1 \leq a_i \leq 1,000\),所以这个贡献是单调递增的。
那么我们刚开始给每个\(x_i\)都分配\(1\),然后用堆维护\(x_i + 1\)的贡献,贪心的取即可。

代码:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define N 100010
int n, m, a[N], b[N], c[N];
struct node {
	ll sum, x, a, b;
	node() {}
	node (ll sum, ll x, ll a, ll b) : sum(sum), x(x), a(a), b(b) {}
	bool operator < (const node &other) const {
		return sum > other.sum;
	}
};

int main() {
	while (scanf("%d%d", &n, &m) != EOF) {
		ll res = 0;
		priority_queue <node> pq;
		for (int i = 1; i <= n; ++i) {
			scanf("%d%d%d", a + i, b + i, c + i);
			res += a[i] + b[i] + c[i];
			pq.push(node(2ll * a[i] + a[i] + b[i], 2, a[i], b[i]));
		}
		m -= n;
		while (m--) {
			node top = pq.top(); pq.pop();
			res += top.sum;
			top.sum = 2ll * top.a * top.x + top.a + top.b;
			++top.x;
			pq.push(top);
		}
		printf("%lld\n", res);
	}
	return 0;
}

D. Tree

题意:
一棵树,每个点有权值\(a_i\),支持两种操作:

  • 将一条链上所有节点权值开根向下取整
  • 求一条链上所有节点权值和

思路:
树链剖分+势能线段树

代码:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define N 100010
int n, q, a[N];
vector <vector<int>> G;

int fa[N], deep[N], sze[N], son[N], top[N], in[N], fin[N], tot;
void DFS(int u) {
	sze[u] = 1;
	for (auto v : G[u]) if (v != fa[u]) {
		fa[v] = u;
		deep[v] = deep[u] + 1;
		DFS(v);
		sze[u] += sze[v];
		if (!son[u] || sze[v] > sze[son[u]]) {
			son[u] = v;
		}
	}
}
void gettop(int u, int tp) {
	in[u] = ++tot;
	fin[tot] = u;
	top[u] = tp;
	if (!son[u]) return;
	gettop(son[u], tp);
	for (auto v : G[u]) {
		if (v != son[u] && v != fa[u]) {
			gettop(v, v);
		}
	}
}

struct SEG {
	struct node {
		ll sum;
		int tot;
		node() {
			sum = tot = 0;
		}
		node operator + (const node &other) const {
			node res = node();
			res.sum = sum + other.sum;
			res.tot = tot + other.tot;
			return res;
		}
	}t[N << 2];
	void build(int id, int l, int r) {
		if (l == r) {
			t[id].sum = a[fin[l]];
			t[id].tot = (t[id].sum == 1);
			return;
		}
		int mid = (l + r) >> 1;
		build(id << 1, l, mid);
		build(id << 1 | 1, mid + 1, r);
		t[id] = t[id << 1] + t[id << 1 | 1];
	}
	void update(int id, int l, int r, int ql, int qr) {
		if (l == r) {
			t[id].sum = floor(sqrt(t[id].sum));
			if (t[id].sum == 1) {
				t[id].tot = 1;
			}
			return;
		}
		if (l >= ql && r <= qr) {
			if (t[id].tot == r - l + 1) return;
		}
		int mid = (l + r) >> 1;
		if (ql <= mid) update(id << 1, l, mid, ql, qr);
		if (qr > mid) update(id << 1 | 1, mid + 1, r, ql, qr);
		t[id] = t[id << 1] + t[id << 1 | 1];
	}
	ll query(int id, int l, int r, int ql, int qr) {
		if (l >= ql && r <= qr) return t[id].sum;
		int mid = (l + r) >> 1;
		ll res = 0;
		if (ql <= mid) res += query(id << 1, l, mid, ql, qr);
	    if (qr > mid) res += query(id << 1 | 1, mid + 1, r, ql, qr);
		return res;	
	}
}seg;

void update(int u, int v) {
	while (top[u] != top[v]) {
		if (deep[top[u]] < deep[top[v]]) swap(u, v);
		seg.update(1, 1, n, in[top[u]], in[u]);
		u = fa[top[u]];
	}
	if (deep[u] > deep[v]) swap(u, v);
	seg.update(1, 1, n, in[u], in[v]);
}
ll query(int u, int v) {
	ll res = 0;
	while (top[u] != top[v]) {
		if (deep[top[u]] < deep[top[v]]) swap(u, v);
		res += seg.query(1, 1, n, in[top[u]], in[u]);
		u = fa[top[u]];
	}
	if (deep[u] > deep[v]) swap(u, v);
	return res + seg.query(1, 1, n, in[u], in[v]);
}

int main() {
	while (scanf("%d%d", &n, &q) != EOF) {
		G.clear(); G.resize(n + 1);
		memset(son, 0, sizeof son);
		tot = 0;
		for (int i = 1; i <= n; ++i) scanf("%d", a + i);
		for (int i = 1, u, v; i < n; ++i) {
			scanf("%d%d", &u, &v);
			G[u].push_back(v);
			G[v].push_back(u);
		}	
		DFS(1); gettop(1, 1);
		seg.build(1, 1, n);
		int op, u, v;
		while (q--) {
			scanf("%d%d%d", &op, &u, &v);
			switch(op) {
				case 0 :
					update(u, v);
					break;
				case 1 :
					printf("%lld\n", query(u, v));
					break;
			}
		}
	}
	return 0;
}

E.Checkout

题意:
在一棵树中,每个点有一个权值:

  • 如果两个点有共同父亲并且权值相同,那么它们会产生\(1\)的全局贡献
  • 或者某个点和它父亲有共同的权值,它们也会产生\(1\)的全局贡献。
    如果一个人离职了,那么会从它的儿子中选一个人顶替它的位置,并且它从树中移去,但是要保证选的人能够是的全局贡献最优。
    问每个人离职后,全局贡献是多少?

思路:
考虑对每个点用\(map\)维护一个二元组,表示它以及它的直接儿子的爱好数量,那么每种爱好的数量\(x\)的贡献就是\(\frac{x(x - 1)}{2}\)
那么对于每个点,枚举它的儿子,去替换当前点,每次用儿子的\(map\)去更新父亲的\(map\),这样的复杂度是\(nlogn\)的,因为儿子只有一个父亲,每个儿子的\(map\)只会加一次,删一次,次数是\(\mathcal{O}(n)\)的。

代码:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define N 100010
#define pii pair <int, int>
#define fi first
#define se second
int n, m, a[N];
vector <vector<int>> G;
vector <map<int,int>> mp;
ll res[N], tot;

ll f(ll x) {
	return x * (x - 1) / 2;
}

ll getse(map <int, int> &mp, int x) {
	if (mp.find(x) == mp.end()) return 0;
	return f(mp[x]);
}

void del(map <int, int> &mp, int x, int num, ll &tmp) {
	tmp -= getse(mp, x);
	if (mp[x] == num) mp.erase(x);
	else mp[x] -= num;
	tmp += getse(mp, x);
}

void add(map <int, int> &mp, int x, int num, ll &tmp) {
	tmp -= getse(mp, x);
	mp[x] += num;
	tmp += getse(mp, x);
}

int fa[N];
void DFS(int u) {
	mp[u].clear();
	mp[u][a[u]] = 1;
	for (auto v : G[u]) if (v != fa[u]) {
		fa[v] = u;
		DFS(v);
		++mp[u][a[v]];
	}
	for (auto it : mp[u]) {
		tot += f(it.se);
	}
}

void DFS2(int u) {
	res[u] = 0;	
	ll tmp = tot;
	if (u != 1) {
		del(mp[fa[u]], a[u], 1, tmp);
	}
	del(mp[u], a[u], 1, tmp);
	res[u] = tmp;
	for (auto v : G[u]) if (v != fa[u]) {
		del(mp[u], a[v], 1, tmp);  
		for (auto it : mp[v]) {
			add(mp[u], it.fi, it.se, tmp);
		    tmp -= getse(mp[v], it.fi); 
		}	
		if (u != 1) {
			add(mp[fa[u]], a[v], 1, tmp);
		}
		res[u] = max(res[u], tmp);
		for (auto it : mp[v]) {
			del(mp[u], it.fi, it.se, tmp);
			tmp += getse(mp[v], it.fi);
		}
		if (u != 1) {
			del(mp[fa[u]], a[v], 1, tmp);
		}
		add(mp[u], a[v], 1, tmp);
	}
	if (u != 1) {
		++mp[fa[u]][a[u]];
	}
	++mp[u][a[u]];
	for (auto v : G[u]) if (v != fa[u]) DFS2(v);
}

int main() {
	while (scanf("%d%d", &n, &m) != EOF) {
		mp.clear(); mp.resize(n + 1);
		G.clear(); G.resize(n + 1);
		for (int i = 1; i <= n; ++i) scanf("%d", a + i);
		for (int i = 1, u, v; i < n; ++i) {
			scanf("%d%d", &u, &v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		fa[1] = 0;
		DFS(1);
		DFS2(1);
		for (int i = 1; i <= n; ++i) printf("%lld%c", res[i], " \n"[i == n]);	
	}
	return 0;
}

F.String

题意:
有一个长度为\(n\)的字符串,每次可以将一段长度不大于\(l\)的子串修改成同一种字母,问至少修改多少次可以使得字符串最多含有\(k\)段。

思路:
考虑\(f[i][j]\)表示到第\(i\)个位置,划分成\(j\)段,并且将最后一段修改成同一颜色或者修改成和前面一段的颜色相同的最小代价。
转移在注释中。

代码:

#include <bits/stdc++.h>
using namespace std;

#define N 100010
int n, l, k;
char s[N];
int fa[N];
int f[N][15];

void Min(int &x, int y) {
	if (x > y) x = y;
}

int main() {
	while (scanf("%d%d%d", &n, &l, &k) != EOF) {
		k = min(n, k);
		scanf("%s", s + 1);
		int tot = 0;
		fa[1] = ++tot;
		for (int i = 2; i <= n; ++i) {
			if (s[i] == s[i - 1]) {
				fa[i] = fa[i - 1];
			} else {
				fa[i] = ++tot;
			}
		}
		memset(f, 0x3f, sizeof f);
		f[1][1] = 0;
		for (int i = 2; i <= n; ++i) {
			for (int j = 1; j <= k; ++j) {
				int pos = max(i - l, 1);
				//将[pos + 1, i]染成同一种颜色
				Min(f[i][j], f[pos][j - 1] + 1);
				//将[pos + 1, i]染成和s[pos]的颜色一样
				Min(f[i][j], f[pos][j] + 1);
				if (fa[i] == fa[pos + 1]) {
				    Min(f[i][j], f[pos][j - 1]);	
					int pos2 = max(pos - 1, 1);
					if (s[i] == s[pos2]) {
						//考虑此时f[pos][j]这个状态下的颜色要么是任意的,要么和前面一个位置的颜色一样
						//如果是任意的,可以任意的,可以直接合并
						//如果和前面一个位置的颜色一样,也可以直接合并
						Min(f[i][j], f[pos][j]);
					}
				}
			}
		}
		printf("%d\n", f[n][k]);
	}
	return 0;
}

G. Circle

题意:
单位圆上有\(n\)个点,是圆的\(n\)等分点,组成了一个正\(n\)边形,现在要求在圆上增加一个点,使得新的\(n + 1\)边形的面积最大。

思路:
考虑使圆的剩下的面积最小。
那么新加的点肯定是在两点的中间。
然后初中数学算一算即可。

代码:

#include <bits/stdc++.h>
using namespace std;

#define db double
const db PI = acos(-1.0);

int main() {
	int n; 
	while (scanf("%d", &n) != EOF) {
		db res = PI;
		db angle = 2 * PI / n;  
		db one = PI / n - sin(angle) / 2;
		db one2 = PI / n / 2 - sin(angle / 2) / 2;
		res -= (n - 1) * one;
		res -= 2 * one2;
		printf("%.6f\n", res);
	}
	return 0;
}

H.Clock

题意:
\(wls\)有一些时刻,它想要通过转动秒针来钟表上复现这些时刻(不需要依次复现),问秒针最少需要转动的角度。

思路:

  • 将时刻都转换成秒,要注意钟表上的时间是\(12\)小时的。
  • 然后将长度扩展成两倍,即\(24\)小时,然后枚举时刻数量为\(n\)的区间,计算并更新答案。

代码:

#include <bits/stdc++.h>
using namespace std;

#define N 200010
#define INF 0x3f3f3f3f
const int p = 43200;
int n, a[N], now, Min, Max;

int f(int Min, int Max, int now) {
	if (now <= Min) return Max - now;
	if (now >= Max) return now - Min;
	return (Max - Min) + min(now - Min, Max - now);
}

int solve(int now) {
	int res = 1e9;
	for (int l = 1, r = 1; r < p * 2; ++r) {
		while (l < r && a[r] - a[l] >= n) {
			++l;
		}
		if (a[r] - a[l - 1] == n) {
			res = min(res, f(l, r, now));
		}
	} 
	return res;
}

int get() {
	int h, m, s;
	scanf("%d%d%d", &h, &m, &s);
	h %= 12;
	return (h * 3600 + m * 60 + s);
}

int main() {
	while (scanf("%d", &n) != EOF) {
		memset(a, 0, sizeof a);
		now = get();
		for (int i = 1; i <= n; ++i) {
			int t = get();
			++a[t];
			++a[t + p];
		}
		for (int i = 1; i < p * 2; ++i) {
			a[i] += a[i - 1]; 
		}
		printf("%d.00\n", min(solve(now), solve(now + p)) * 6);
	}
	return 0;
}

J.Tangram

题意:
问在七巧板上加\(n\)条之间最多能将七巧板切成多少块?

思路:
不会啊,规律题?

代码:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
ll n;

int main() {
	while (scanf("%lld", &n) != EOF) {
		printf("%lld\n", 7 + (1ll * n * (11 + n) >> 1));
	}
	return 0;
}

K.Tetris

题意:
\(n \cdot m\)的网格中,问用俄罗斯方块填满,输出任意一种方案。

思路:
样例给出的是唯一的构造方案。
也就是说只有当\(n \equiv 0 \bmod 4\)并且\(m \equiv 0 \bmod 4\)的时候才有解。

代码:

#include <bits/stdc++.h>
using namespace std;

string s[4] = {
	"1113",
	"2133",
	"2243",	
	"2444"
};

int main() {
	int n, m;
	while (cin >> n >> m) {
		if (n % 4 || m % 4) {
			cout << "no response\n";
		} else {
			for (int i = 0; i < n / 4; ++i) {
				for (int _ = 0; _ < 4; ++_) {
					for (int j = 0; j < m / 4; ++j) {
						cout << s[_];	
					}
					cout << "\n";
				}
			}
		}
	}
	return 0;
}
posted @ 2019-07-16 07:59  Dup4  阅读(558)  评论(0编辑  收藏  举报