比赛题目训练系列01 (The 17th Zhejiang Provincial Collegiate Programming Contest)

比赛题目训练系列01 (The 17th Zhejiang Provincial Collegiate Programming Contest)

训练网址
系列简介:选择了2019~2021年 ICPC, CCPC, 牛客杭电, 省赛校赛优质题目,目标做够100套题。

B. Bin Packing Problem

  • 题意:n个物品,第i个物品的容量是 a[i] ,有无限个箱子,每个箱子的容量是 c ,问按照一下两种放法,最少需要几个箱子。
    第一种:每次都选择最前面的可以放在物品的箱子,若不存在就增加一个箱子。
    第二种:每次都选择剩余容量最小的且可以容纳物品的箱子,若不存在就增加一个箱子。
    问两种方法各需要多少个箱子
  • 第一个:是在数组中找到第一个大于等于x的数,这个用线段树,维护区间最大值,先一直往左找,找不到的话往右找。
  • 第二种:找到一个动态有序序列中第一个大于等于x的数,平衡树嘛。不过,这个题用map就挺好的,需要记录重复的数据(虽然说map是红黑树但是这不重要)。
#include
#include
#include
#include
using namespace std;
const int maxn = 1000010;
int a[maxn];
struct node {
	int l, r, maxc;
}tr[maxn * 2];
int N, C;
void pushup(int u) {
	tr[u].maxc = max(tr[2 * u].maxc, tr[2 * u + 1].maxc);
}
void build(int u, int l, int r) {
	if (l == r) {
		tr[u] = { l, r, C };
	}
	else {
		tr[u].l = l, tr[u].r = r;
		int mid = (l + r) / 2;
		build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);
		pushup(u);
	}
}
bool modify(int u, int x) {
	//printf("***\n");
	if (tr[u].l == tr[u].r) {
		tr[u].maxc -= x;
		return true;
	}
	else {
		if (tr[2 * u].maxc >= x && modify(2 * u, x)) {
			pushup(u);
			return true;
		}
		if (tr[2 * u + 1].maxc >= x && modify(2 * u + 1, x)) {
			pushup(u);
			return true;
		}
		return false;
	}
}
int query(int u, int id) {
	if (tr[u].l == tr[u].r) return tr[u].maxc;
	int mid = (tr[u].l + tr[u].r) / 2;
	if (id <= mid) return query(2 * u, id);
	else if (id > mid) return query(2 * u + 1, id);
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &N, &C);
		build(1, 1, N);
		for (int i = 1; i <= N; i++) {
			scanf("%d", &a[i]);
		}
		map<int, int> mp;
		int res1 = 0, res2 = 0;
		for (int i = 1; i <= N; i++) {
			modify(1, a[i]);
			auto p = mp.lower_bound(a[i]);
			if (p != mp.end()) {
				--p->second;
				++mp[p->first - a[i]];
				if (p->second == 0) mp.erase(p);
			}
			else {
				++mp[C - a[i]];
				++res2;
			}
		}
		for (int i = 1; i <= N; i++) {
			//printf("$$$ %d\n", query(1, i));
			if (query(1, i) == C) {
				res1 = i - 1;
				break;
			}
			if (i == N) {
				res1 = N;
			}
		}
		printf("%d %d\n", res1, res2);
	}
	return 0;
}

C. Crossword Validation

  • 题意:给定你一个NxN的矩阵,矩阵中的元素有#和小写字母,#代表障碍。然后给定你m个询问,每个询问都含有一个字符串和值。然后从矩阵中截取子串,子串的截取规则是水平方向上顶到障碍的和竖直方向上顶到障碍的所有子串,然后问你这些子串是否全部存在于我询问的m个字符串中,若存在的话输出他们的总和值,否则输出-1。
  • 很简单,但是有一个小细节。就是在字典树中查询的时候。由于字典树从根节点到叶节点的路径就是之前保存的字符串的路径。如果查询到当前字符串存在于字典树中,并不意味着真的存在,因为可能查询到的只是一个前缀。因此,还要看看 w [ p ] w[p] w[p] 是否为 0.
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1010, maxm = 4000010;
char mz[maxn][maxn], str[maxn];
int N, M;
int son[maxm][26], w[maxm], idx;
void insert(char* str, int value) {
	int p = 0;
	for (int i = 0; str[i]; i++) {
		int u = str[i] - 'a';
		if (!son[p][u]) son[p][u] = ++idx;
		p = son[p][u];
	}
	w[p] = value;
}
int query(char* str) {
	int p = 0;
	for (int i = 0; str[i]; i++) {
		int u = str[i] - 'a';
		if (!son[p][u]) return -1;
		p = son[p][u];
	}
	//这个地方必须要这样写。
	//也许输入的 str 是保存的某一个字符串的前缀,那么此时的 p 确实不是 0
	//但是这样的前缀也许在字典树中不存在。
	if (w[p]) return w[p];
	else return -1;
}

void init() {
	for (int i = 0; i <= idx; i++) {
		fill(son[i], son[i] + 26, 0);
		w[i] = 0;
	}
	idx = 0;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		init();
		scanf("%d%d", &N, &M);
		for (int i = 1; i <= N; i++) {
			scanf("%s", mz[i] + 1);
		}
		while (M--) {
			int x;
			scanf("%s%d", str, &x);
			insert(str, x);
		}
		ll ans = 0;
		bool flag = true;
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= N; j++) {
				int sz = 0;
				//这个判断得加上
				if (mz[i][j] == '#') continue;

				if (i == 1 || mz[i - 1][j] == '#') {
					int x = i;
					while (x <= N && mz[x][j] != '#') {
						str[sz++] = mz[x][j];
						x++;
					}
					str[sz] = 0;
					int v = query(str);
					if (v == -1) {
						flag = false;
						break;
					}
					ans += v;
				}
				
				//printf("*** %d %s %lld\n", sz, str, ans);
				
				sz = 0;
				if (j == 1 || mz[i][j - 1] == '#') {
					int y = j;
					while (y <= N && mz[i][y] != '#') {
						str[sz++] = mz[i][y];
						y++;
					}
					str[sz] = 0;
					int v = query(str);
					if (v == -1) {
						flag = false;
						break;
					}
					ans += v;
				}
				
				//printf("*** %d %s %lld\n", sz, str, ans);

			}
			if (!flag) break;
		}
		if (!flag) ans = -1;
		printf("%lld\n", ans);
	}
	return 0;
}

E. Easy DP Problem

  • 题意:给你这么一个dp的方程,然后给出 l, r, k。即选择 a[l ~ r] 作为 b 数组,问 d p ( r − l + 1 , k ) dp(r - l + 1, k) dp(rl+1,k) 这个数是多少。
    在这里插入图片描述
  • 多输入几个无序的序列,然后分析分析,可以看出来,题目问的是, ∑ i = 1 m i 2 \sum\limits_{i=1}^mi^2 i=1mi2 加上 a[l ~ r + 1] 这些数中的前 k 个大的数。这就需要主席树了。感觉省赛题确实比CCPC区域赛在思维上要简单一点。
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 100010;
typedef long long ll;
int N, M, a[maxn];
ll sum2[maxn];
vector<int> nums;
struct node {
	int l, r;
	int cnt;
	ll sum;
}tr[maxn * 4 + maxn * 17];
//空间开到 4N + N * log(N)
int root[maxn], idx;
int find(int x) {
	return lower_bound(nums.begin(), nums.end(), x, greater<int>()) - nums.begin();
}
int build(int l, int r) {
	int p = ++idx;
	tr[p] = { l, r };
	if (l == r) return p;
	int mid = (l + r) / 2;
	tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
	return p;
}
//p 为原来的线段树的结点,q 为复制的线段是的结点
int insert(int p, int l, int r, int x) {
	int q = ++idx;
	tr[q] = tr[p];
	if (l == r) {
		tr[q].cnt++;
		tr[q].sum += nums[x];
		return q;
	}
	int mid = (l + r) / 2;
	if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
	else tr[q].r = insert(tr[p].r, mid + 1, r, x);
	tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
	tr[q].sum = tr[tr[q].l].sum + tr[tr[q].r].sum;
	return q;
}
ll query(int q, int p, int l, int r, int k) {
	//这个地方要小心,因为有重复数字的话,需要这样处理,不然结果会偏大。
	if (l == r) return (tr[q].sum - tr[p].sum) / (tr[q].cnt - tr[p].cnt) * k;
	int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
	int mid = (l + r) / 2;
	if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
	else return tr[tr[q].l].sum - tr[tr[p].l].sum + query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
}
int main() {
	for (ll i = 1; i <= 100000; i++) {
		sum2[i] = i * i + sum2[i - 1];
	}
	int T;
	scanf("%d", &T);
	while (T--) {
		//看看如何初始化的
		nums.clear();
		for (int i = 0; i <= idx; i++) {
			tr[i] = { 0, 0, 0, 0 };
		}
		fill(root, root + N + 1, 0);
		idx = 0;

		scanf("%d", &N);
		for (int i = 1; i <= N; i++) {
			scanf("%d", &a[i]);
			nums.push_back(a[i]);
		}
		sort(nums.begin(), nums.end(), greater<int>());
		nums.erase(unique(nums.begin(), nums.end()), nums.end());
		root[0] = build(0, nums.size() - 1);

		for (int i = 1; i <= N; i++) {
			root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
		}

		scanf("%d", &M);
		while (M--) {
			int l, r, k;
			scanf("%d%d%d", &l, &r, &k);
			int m = r - l + 1;
			printf("%lld\n", sum2[m] + query(root[r], root[l - 1], 0, nums.size() - 1, k));
		}
	}
	
	return 0;
}

F. Finding a Sample

  • 题意:给定两个 n n n 维样本的二元分类器,参数为 ( w 1 , b 1 ) (\bold{w_1}, b_1) (w1,b1) ( w 2 , b 2 ) (\bold{w_2}, b_2) (w2,b2),构造 x \bold{x} x 使得 ( w 1 T x + b 1 ) ( w 2 T x + b 2 ) < 0 (\bold{w_1^Tx} + b_1)(\bold{w_2^Tx} + b_2) < 0 (w1Tx+b1)(w2Tx+b2)<0。无解输出 No.
  • 这个题不难的。仔细思考会发现,其实只需要修改一组x就可以了,其他都为0。
#include
#include
#include
using namespace std;
const int maxn = 210;
int a[maxn], b[maxn];
double ans[maxn];
int N;
bool flag;
void Print() {
	flag = true;
	for (int i = 0; i < N; i++) {
		//if(ans[i] == 0) printf("0%c", i + 1 == N ? '\n' : ' ');
		printf("%.1f%c", ans[i], i + 1 == N ? '\n' : ' ');
	}
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		memset(a, 0, sizeof a);
		memset(b, 0, sizeof b);
		memset(ans, 0, sizeof ans);
		scanf("%d", &N);
		for (int i = 0; i <= N; i++) scanf("%d", &a[i]);
		for (int i = 0; i <= N; i++) scanf("%d", &b[i]);
		flag = false;
		//先判断这个,后面可以省去一些讨论。
		if (a[N] * b[N] < 0) Print();
		else {
			
			for (int i = 0; i < N; i++) {
				if (a[i] * b[i] < 0) {
					ans[i] = 10000;
					Print();
					break;
				}
				//考虑二次方程 (ax + b)(cx + d) < 0 有解的情况
				else if(a[i] * b[i] > 0 && a[i] * b[N] != b[i] * a[N]){
					ans[i] = (-(double)a[N] / a[i] - (double)b[N] / b[i]) / 2.0;
					Print();
					break;
				}
				else if ((a[i] != 0 || b[i] != 0) && a[i] * b[N] != b[i] * a[N]) {
					if (a[i] == 0) swap(a, b);
					if (a[i] * b[N] > 0) ans[i] = -10000;
					else if (a[i] * b[N] < 0) ans[i] = 10000;
					else continue;
					Print();
					break;
				}
			}
		}
		if (!flag) printf("No\n");
	}
	return 0;
}

G. Gliding

  • 图论

H. Huge Clouds

  • 在二维平面上给定一些点一些线段,定义在x轴上一个点u,如果点u和任意一个给定点v的连线和所有给定线段都不相交(包括端点),则u不在阴影中。问x轴上阴影的长度(>1e9则输出-1)。
  • 计算几何题目,一定要先看清楚数据范围,比如这个题的y只分布在上半部分,这极大简化了题目。
  • 思路很简单,首先对每个点预处理出会被线段遮挡的范围。考虑一个点被一条线段遮挡,产生的阴影是一条线段或者一个射线,因为题目要求最后答案>1e9则输出-1,而且给定的点都是整点,因此可以将射线转为一个端点 为INF(1e12)的线段。一个点被遮挡的范围就是它被所有线段遮挡产生的线段的并。求出每个点遮挡范围之后,对所有点的遮挡范围取交集,交集中的长度就是答案。
  • 然后吧,求线段集合的交有点麻烦,可以用的摩根定律,转化为求反的并。
  • 有个小细节。一个是,求并的时候,小心是空集。另外一个,当光源的纵坐标在线段两个端点纵坐标之间的时候,注意射线的方向是看这个点在线段的左边还是右边,而不是看在上端点的左边还是右边!
#include
#include
#include
#include
#include
#define x first
#define y second
const double eps = 1e-8, INF = 1e12;
using namespace std;
const int maxn = 510;
typedef pair<double, double> P;

int sign(double x) {
	if (fabs(x) < eps) return 0;
	if (x < 0) return -1;
	return 1;
}


P operator + (P a, P b) {
	return { a.x + b.x, a.y + b.y };
}
P operator -(P a, P b) {
	return { a.x - b.x, a.y - b.y };
}
P operator*(P a, double t) {
	return { a.x * t, a.y * t };
}
double operator*(P a, P b) {
	return a.x * b.x + a.y * b.y;
}
double cross(P a, P b) {
	return a.x * b.y - b.x * a.y;
}

P get_line_intersection(P p, P v, P q, P w) {
	P u = p - q;
	double t = cross(w, u) / cross(v, w);
	return p + v * t;
}

bool on_segment(P p, P a, P b) {
	return sign(cross(p - a, p - b)) == 0 && sign((p - a) * (p - b)) <= 0;
}

int N, M;
P stars[maxn], end1[maxn], end2[maxn];
bool on_cloud[maxn];

void merge(vector<P>& v1, vector<P>& v2) {
	sort(v1.begin(), v1.end());
	int sz = v1.size();
	if (sz == 0) return;
	double st = v1[0].x, ed = v1[0].y;
	for (int i = 1; i < sz; i++) {
		if (v1[i].x > ed) {
			v2.push_back({ st, ed });
			st = v1[i].x, ed = v1[i].y;
		}
		else ed = max(ed, v1[i].y);
	}
	v2.push_back({ st, ed });
}

double solve() {
	memset(on_cloud, 0, sizeof on_cloud);
	//预处理出星星在云中的情况
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= M; j++) {
			on_cloud[i] |= on_segment(stars[i], end1[j], end2[j]);
			on_cloud[i] |= (!sign(stars[i].x - end1[j].x) && !sign(stars[i].y - end1[j].y));
			on_cloud[i] |= (!sign(stars[i].x - end2[j].x) && !sign(stars[i].y - end2[j].y));
		}
		//printf("*** %d\n", on_cloud[i]);
	}

	vector<P> segs;
	for (int i = 1; i <= N; i++) {
		vector<P> shadows;
		double left = -INF, right = INF;
		for (int j = 1; j <= M; j++) {
			P o = { 0, 0 }, vec = { 1, 0 };
			double l, r;
			if (on_cloud[i]) l = -INF, r = INF;
			//三种位置关系
			else if (stars[i].y <= end1[j].y) {
				l = r = 0;
			}
			else if (stars[i].y <= end2[j].y) {
				auto p = get_line_intersection(stars[i], end1[j] - stars[i], o, vec);
				//小心这个地方,很长时间才发现,不是比较光源和上面的点那个纵坐标大,而是比较光源在直线的左边还是右边。
				if (cross(end1[j] - stars[i], end2[j] - stars[i]) < 0) l = -INF, r = p.x;
				else l = p.x, r = INF;
			}
			else {
				auto p1 = get_line_intersection(stars[i], end1[j] - stars[i], o, vec);
				auto p2 = get_line_intersection(stars[i], end2[j] - stars[i], o, vec);

				l = p1.x, r = p2.x;
				if (l > r) swap(l, r);
				
			}
			shadows.push_back({ l, r });
			//if(i == 8) printf("### %d %d %f %f\n", i, j, l, r);
		}
		vector<P> new_shadows;
		merge(shadows, new_shadows);
		int sz = new_shadows.size();

		/*for (auto p : new_shadows) {
			printf("$$$ %f %f\n", p.x, p.y);
		}*/


		if (sz == 0) {
			segs.push_back({ -INF, INF });
			continue;
		}
		
		if (sign(new_shadows[0].x + INF)) segs.push_back({ -INF, new_shadows[0].x });
		for (int i = 1; i < sz; i++) segs.push_back({ new_shadows[i - 1].y, new_shadows[i].x });
		if (sign(new_shadows[sz - 1].y - INF)) segs.push_back({ new_shadows[sz - 1].y, INF });
		/*if(!segs.empty())printf("&&& %f %f\n", segs.back().x, segs.back().y);*/
		
	}
	vector<P> new_segs;
	merge(segs, new_segs);
	double res = 0;
	int sz = new_segs.size();
	if (sz == 0) return -1;
	/*for (auto p : new_segs) {
		printf("*** %f %f\n", p.x, p.y);
	}*/


	if (sign(new_segs[0].x + INF)) return -1;
	if (sign(new_segs[sz - 1].y - INF)) return -1;
	for (int i = 1; i < sz; i++) res += new_segs[i].x - new_segs[i - 1].y;
	if (res >= 1e9) return -1;
	return res;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &N, &M);
		for (int i = 1; i <= N; i++) {
			scanf("%lf%lf", &stars[i].x, &stars[i].y);
		}
		for (int i = 1; i <= M; i++) {
			scanf("%lf%lf%lf%lf", &end1[i].x, &end1[i].y, &end2[i].x, &end2[i].y);
			//规定让 end2 在上面
			if (end1[i].y > end2[i].y) swap(end1[i], end2[i]);
		}
		printf("%.10f\n", solve());
	}
	return 0;

}

I. Invoking the Magic

  • 题意:有n双袜子,初始状态给你n对随机配对的袜子,说有一个魔法洗衣机,你把袜子放进去,他就可以自动给你配对,但是要保证放进去的袜子可以恰好配对,也就是不能有无法配对的袜子,问洗衣机的容量(就是一次可以处理袜子的双数)最少是多少。
  • 我们发现如果洗衣机里面是相同的袜子,必然会形成一个环。很裸的并查集了
#include
#include
#include
#include
using namespace std;
const int maxn = 100010;
int p[maxn], sz[maxn];
int find(int x) {
	if (p[x] == x) return x;
	return p[x] = find(p[x]);
}
void unite(int a, int b) {
	if (find(a) == find(b)) return;
	sz[find(b)] += sz[find(a)];
	p[find(a)] = find(b);
}
int main() {
	int T;
	scanf("%d", &T);
	while(T--){
		int N;
		scanf("%d", &N);
		for (int i = 1; i <= N; i++) {
			p[i] = i, sz[i] = 1;
		}
		unordered_map<int, int> um;
		int id = 0;
		for (int i = 1; i <= N; i++) {
			int a, b;
			scanf("%d%d", &a, &b);
			if (!um.count(a)) um[a] = ++id;
			if (!um.count(b)) um[b] = ++id;
			unite(um[a], um[b]);
		}
		int ans = 0;
		for (int i = 1; i <= N; i++) {
			ans = max(ans, sz[i]);
		}
		printf("%d\n", ans);
	}
	return 0;
}

你可能感兴趣的:(ACM题目整理)