2021 BNU Winter Training 2 (2019-2020 ICPC Asia Hong Kong Regional Contest)

2021 BNU Winter Training 2 (2019-2020 ICPC Asia Hong Kong Regional Contest)

训练网址

A. Axis of Symmetry

  • 题意:T组数据,每组数据给你一个正整数 n,然后给你笛卡尔坐标系下n个矩形的左下角和右上角的点的坐标 ( x i 1 , y i 1 ) , ( x i 2 , y i 2 ) (x_{i_1},y_{i_1}),(x_{i_2},y_{i_2}) (xi1,yi1),(xi2,yi2),保证矩形之间不会有重叠,求出所有的对称轴
  • 结论1:对称轴最多为四条,即两条分别与横纵坐标轴平行的直线和两条斜率分别为1和-1的直线。其实挺好想的,因为给定的矩形的边都是平行坐标轴的,提示里面也画出来了。
  • 结论2:设所有矩形的端点中,横纵坐标的最小、最大值分别为 x m i n , x m a x , y m i n , y m x xmin, xmax, ymin, ymx xmin,xmax,ymin,ymx,那么中心就是 ( x m i n + x m a x ) / 2 , ( y m i n + y m a x ) / 2 (xmin + xmax) / 2, (ymin + ymax) / 2 (xmin+xmax)/2,(ymin+ymax)/2. 然后对称轴就是穿过这个中心的四条直线。
  • 然后,我们发现,我们可以把所有的点分奇数点(被矩形覆盖奇数次)和偶数点(被矩形覆盖偶数次)。而对于这个新图形来说,奇数点一定在拐角处,偶数点一定在直边上(画画图就知道)。若图形关于某一条直线对称,等价于所有边关于这条直线对称,等价于所有边的两个端点,关于这条直线的对称点都存在。因此,我们只需要判断奇数点即可。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> P;

#define x first
#define y second

struct Line {
	ll a, b, c;
	bool operator < (const Line& L) {
		return a > L.a || a == L.a && b > L.b ||
			a == L.a && b == L.b && c > L.c;
	}
};

const ll INF = 1e18;
map<P, int> mp;
bool ok[4];

ll gcd(ll a, ll b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}
Line gcd(ll a, ll b, ll c) {
	//因为是输出字典序最大的方案,因此尽量让第一个数是正数
	ll d = gcd(gcd(a, b), c);

	if (a / d < 0) {
		d = -d;
	}
	else if (a == 0 && b / d < 0) {
		d = -d;
	}
	
	return { a / d, b / d, c / d };
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		mp.clear();
		int N;
		scanf("%d", &N);
		ll xmin = INF, xmax = -INF, ymin = INF, ymax = -INF;
		for (int i = 0; i < N; i++) {
			ll x1, y1, x2, y2;
			scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
			//坐标都乘2,都转化成偶数,这样子求中心时,防止出现浮点数,进而避免浮点数误差。
			x1 *= 2, y1 *= 2, x2 *= 2, y2 *= 2;
			xmin = min(xmin, x1);
			xmax = max(xmax, x2);
			ymin = min(ymin, y1);
			ymax = max(ymax, y2);
			mp[P(x1, y1)] ^= 1, mp[P(x1, y2)] ^= 1, mp[P(x2, y1)] ^= 1, mp[P(x2, y2)] ^= 1;
		}

		fill(ok, ok + 4, true);
		//ok 四个元素分别应 x = 0, y = 0, y = x, y = -x
		ll mx = (xmin + xmax) / 2, my = (ymin + ymax) / 2;
		for (auto pp : mp) {
			//只需要判断被奇数个矩形覆盖的点
			if (pp.second == 0) continue;

			auto p = pp.first;
			ll x = -(p.x - mx) + mx, y = (p.y - my) + my;
			ok[0] = (ok[0] && mp.count({ x, y }) && mp[{x, y}]);
			x = (p.x - mx) + mx, y = -(p.y - my) + my;
			ok[1] = (ok[1] && mp.count({ x, y }) && mp[{x, y}]);
			x = (p.y - my) + mx, y = (p.x - mx) + my;
			ok[2] = (ok[2] && mp.count({ x, y }) && mp[{x, y}]);
			x = -(p.y - my) + mx, y = -(p.x - mx) + my;
			ok[3] = (ok[3] && mp.count({ x, y }) && mp[{x, y}]);
		}

		vector<Line> ans;

		if (ok[0]) ans.push_back(gcd(2, 0, mx));
		if (ok[1]) ans.push_back(gcd(0, 2, my));
		if (ok[2]) ans.push_back(gcd(2, -2, mx - my));
		if (ok[3]) ans.push_back(gcd(2, 2, mx + my));

		//看清,是输出字典序最大的方案
		sort(ans.begin(), ans.end());

		printf("%d\n", (int)ans.size());
		for (int i = 0; i < (int)ans.size(); i++) {
			printf("%lld %lld %lld ", ans[i].a, ans[i].b, ans[i].c);
		}
		printf("\n");
	}
	return 0;
}

B. Constructing Ranches

  • 难的graph, 咕

C. Erasing Numbers

  • 删掉连续的三个数字的最大的和最小的,问最后那些数字可能留到最后。
  • 这个题有一个坑。容易想到的是比当前这个数大的记为1,比当前这个数小的记为0。但是,这个数左边01合并之后,并不只是一种结果,而是很多结果都有可能。我们无法枚举所有结果的情况。所以只能采取这样的方法:
  • 能留下的充要条件是最后只剩 01X,0X1,1X0,10X,X01,X10 这6种形式,即必须只剩一个0和一个1。我们发现,消除连续的三个0,会使0相对与1的数量差改变(001这种形式并不会改变0和1的数量差)。而我们可以通过一种方法算出最多消除几个0. 因为是N个数的排列,中位数是 ( N + 1 ) / 2 (N + 1) / 2 (N+1)/2,因此到中位数的距离是 d = a i − ( N + 1 ) / 2 d = a_i - (N + 1) / 2 d=ai(N+1)/2. d < 0 时,序列中0比较多,看看最多消掉的0的组数是否大于等于 ∣ d ∣ |d| d,如果是的话,那么这个数字就可以留下来。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 5010;
int a[maxn];
char s[maxn];
int cal(int l, int r, int i, bool is_small) {
	int now = 0, res = 0;
	if (is_small){
		for (int j = l; j <= r; j++) {
			if (a[j] > a[i]) now++;
			else now--;
			if (now == 3) res++, now = 1;
			else if (now == -1) now = 0;
		}
	}
	else {
		for (int j = l; j <= r; j++) {
			if (a[j] < a[i]) now++;
			else now--;
			if (now == 3) res++, now = 1;
			else if (now == -1) now = 0;
		}
	}
	return res;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int N;
		scanf("%d", &N);
		for (int i = 1; i <= N; i++) scanf("%d", &a[i]);
		int mid = (N + 1) / 2;
		for (int i = 1; i <= N; i++) {
			bool is_small = true;
			if (a[i] > mid) is_small = false;
			if (cal(1, i - 1, i, is_small) + cal(i + 1, N, i, is_small) >= abs(a[i] - mid)) s[i] = '1';
			else s[i] = '0';
		}
		s[N + 1] = 0;
		printf("%s\n", s + 1);
	}
	return 0;
}

D. Junior Mathematician

  • 感觉这个题又是一个dp的板子,有时候用大雪菜的方法并不好处理状态不含数位的情况。就比如这个,不含第 i 位数字是几的信息,因此没办法处理后面的步骤。
  • 题意:求 [ L , R ] ( R ≤ 1 0 5000 ) [L,R] (R \le 10^{5000}) [L,R](R105000) 间满足 x ≡ f ( x )   m o d   m ( m ≤ 60 ) x \equiv f(x) \bmod m (m \le 60) xf(x)modm(m60) 的数量。
  • d p ( i , s u m , r e s ) dp(i, sum, res) dp(i,sum,res):第 i i i 位,当前数字之和是 s u m sum sum,当前 f ( x ) − x f(x) - x f(x)x r e s res res
  • 这样子的话,我们用深搜去搜索答案。和大雪菜的思路差不多,从最高位开始搜, d f s dfs dfs 中间那一大块儿,不同的数位 d p dp dp 都是一样的。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

typedef long long ll;
const ll mod = 1e9 + 7;
using namespace std;
const int maxn = 5010;
char s1[maxn], s2[maxn];
int M;
int f[maxn][60][60], p[maxn], a[maxn];
int my_mod(int a, int b) {
	return (a % b + b) % b;
}

int dp(int pos, int sum, int res, bool limit) {
	// lim=1 表示当前贴合上界,lim=0 则不贴合
	if (pos == -1) {
		return res == 0;
	}
	if (limit == false && f[pos][sum][res] != -1) {
		return f[pos][sum][res];
	}
	else {
		int ans = 0, up = limit ? a[pos] : 9;
		for (int i = 0; i <= up; i++) {
			int xx = my_mod(res + sum * i - i * p[pos], M);
			ans = (ans + dp(pos - 1, my_mod(sum + i, M), xx, (i == up) && limit)) % mod;
		}
		if (limit == false) {  // 不贴合上界的情况有可能会被复用
			f[pos][sum][res] = ans;
		}
		return ans;
	}
}

int solve(char s[], int n) {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < M; j++) {
			memset(f[i][j], -1, sizeof f[i][j]);
		}
	}
	for (int i = 0; i < n; i++) {
		a[i] = s[n - i - 1];
	}
	//这个方法也是从高数位往低数位填数字
	return dp(n - 1, 0, 0, 1);
}

int main() {
	int T;
	scanf("%d", &T);

	while (T--) {
		scanf("%s%s%d", s1, s2, &M);
		int n1 = strlen(s1), n2 = strlen(s2);

		for (int i = 0; i < n1; i++) s1[i] -= '0';
		for (int i = 0; i < n2; i++) s2[i] -= '0';
		s1[n1 - 1]--;
		for (int i = n1 - 1; i >= 0; i--) {
			if (s1[i] < 0) {
				s1[i] += 10;
				s1[i - 1]--;
			}
			else break;
		}

		//for (int i = 0; i < n1; i++) printf("%d", s1[i]);

		p[0] = 1;
		for (int i = 1; i < maxn; i++) {
			p[i] = p[i - 1] * 10 % M;
		}
		//printf("*** %d %d\n", solve(s2, n2), solve(s1, n1));
		printf("%d\n", my_mod(solve(s2, n2) - solve(s1, n1), mod));
	}
	return 0;
}
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页