2021 BNU Winter Training 7 (2020 ICPC上海)

2021 BNU Winter Training 7 (2020 ICPC Shanghai Site)

训练网址

A. Sum of Log

  • 题意:给定 x , y ≤ 1 0 9 x,y≤10^9 x,y109 ∑ x i = 0 ∑ y j = [ i = 0 ] [ i & j = 0 ] [ l o g 2 ( i + j ) + 1 ] \sum\limits_{x_i=0}\sum\limits_{y_j=[i=0]} [i\&j=0][log2(i+j)+1] xi=0yj=[i=0][i&j=0][log2(i+j)+1]
  • 这个题其实稍微一分析就感觉是数位dp,但是头一次遇到两个数一起dp的情况。
  • 其实分析分析,不妨让 X > Y,就是求 i > j 且 i & j = 0 的情况下,i 的二进制位数之和。
  • 数位dp, f ( p o s , l 1 , l 2 ) f(pos, l_1, l_2) f(pos,l1,l2) 表示在二进制下第 pos 个位置,i 和 j 是否顶到上限时,i & j 的数量。其实就是深搜,ok 表示当前状态的最高位是否为当前这个位置,之有是这个位置时才把答案加进去。而且 i & j = = 0 i\&j==0 i&j==0 意味着 i 和 j 不可以都是1。代码很好懂。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll f[50][2][2];
int a[50], b[50];
ll ans;
//pos是位置,l1、l2是 i 和 j 是否顶到上限,state 表示当前状态是否合法,ok表示当前状态的最高位是否为当前这个位置。只有为1时才能加到答案中去
ll dfs(int pos, bool l1, bool l2, int state, int ok) {
    
    
	if (pos == -1) return state == 0;
	if (state > 0) return 0;
	if (f[pos][l1][l2] != -1) return f[pos][l1][l2];
	int up1 = l1 ? a[pos] : 1;
	int up2 = l2 ? b[pos] : 1;
	ll cnt = 0, res = 0;
	for (int i = 0; i <= up1; i++) {
    
    
		for (int j = 0; j <= up2; j++) {
    
    
			ll dx = dfs(pos - 1, l1 && i == up1, l2 && j == up2, state + (i & j), ok ? ok : (i ^ j));
			if (!ok && (i ^ j)) cnt = (cnt + dx) % mod;
			res = (res + dx) % mod;
		}
	}
	
	ans = (ans + cnt * ((ll)pos + 1)) % mod;
	//printf("*** %lld %lld %d %d %d\n", ans, cnt, pos, up1, up2);
	return f[pos][l1][l2] = res;
}
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		memset(f, -1, sizeof f);
		memset(a, 0, sizeof a);
		memset(b, 0, sizeof b);
		ans = 0;
		ll X, Y;
		scanf("%lld%lld", &X, &Y);
		if (X < Y) swap(X, Y);
		int n = 0, m = 0;
		while (X) {
    
    
			a[n++] = X % 2;
			X /= 2;
		}
		while (Y) {
    
    
			b[m++] = Y % 2;
			Y /= 2;
		}
		dfs(n - 1, 1, 1, 0, 0);
		printf("%lld\n", ans);
	}
	return 0;
}

B. Walker

  • 题意:在长度为 n 的数轴上给出两个人的初始位置和速度,问使得每个位置至少被一个人走过的时间是多少
  • 分成三种情况讨论就行,看注释
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
double n, p1, p2, v1, v2;
double f(double x) {
    
    
	//看看p1或者p2先向哪边走时间最短
	double t1 = min((p1 + x) / v1, (x - p1 + x) / v1);
	double t2 = min((n - p2 + n - x) / v2, (p2 - x + n - x) / v2);
	return max(t1, t2);
}

int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		scanf("%lf%lf%lf%lf%lf", &n, &p1, &v1, &p2, &v2);
		//别想复杂了。就是把两个人的序号的换换而已。
		if (p1 > p2) {
    
    
			swap(p1, p2);
			swap(v1, v2);
		}
		double ans;
		//第一种情况,一个人走完全程
		ans = min((n - p1 + n) / v1, (p1 + n) / v1);
		ans = min(ans, min((p2 + n) / v2, (n - p2 + n) / v2));
		//第二种情况,p1向右走, p2向左走
		ans = min(ans, max((n - p1) / v1, p2 / v2));
		//第三种情况,p1 与 p2 在 p1~p2中间某个位置汇集
		//printf("*** %f\n", ans);
		double l = p1, r = p2;
		for (int i = 0; i < 100; i++) {
    
    
			double m1 = (l + r) / 2;
			double m2 = (m1 + r) / 2;
			if (f(m1) > f(m2)) l = m1;
			else r = m2;
			//printf("*** %f\n", f(l));
		}
		ans = min(ans, f(l));
		printf("%.10f\n", ans);
	}
	return 0;
}
/*
1
10 1 0.001 9 10000
*/

C. The Journey of Geor Autumn

  • 对于给定的 n , k ≤ 1 0 7 n,k≤10^7 n,k107,求有多少个满足这样条件的 1.. n 1..n 1..n 全排列:对于任意 i > k i>k i>k a i > min ⁡ j ∈ [ i − k , i − 1 ] a j . a_i>\min\limits_{j∈[i−k,i-1]}a_j. ai>j[ik,i1]minaj.
  • 这个题一看就是dp啊,但是好难找他们之间的递推关系
  • 我们不妨及这个数为 A ( n , k ) A(n, k) A(n,k).
  1. 必须把最小的数字(应该就是1)放在 1~k 的位置上,不妨设放在了第 j 个位置上
  2. 对于前 j − 1 j - 1 j1 个位置,可以随便排,相当于从剩下 n − 1 n - 1 n1 中选择 j − 1 j-1 j1 个数,然后全排列: C n − 1 j − 1 ∗ ( j − 1 ) ! C_{n-1}^{j-1}*(j-1)! Cn1j1(j1)! 随便挑出来 j - 1 个数一定是不影响后面 n − j n-j nj 个数字的排列的。因为后面 n − j n-j nj 个数字一定比第 j 个数字大。
  3. 最后,看后面 n − j n - j nj 个位置。对于 a n − j + 1 a_{n - j + 1} anj+1 a n − j + k a_{n-j+k} anj+k 这 k 个数字,都是比 a j a_j aj 大;而 a n − j + k + 1 a_{n-j+k+1} anj+k+1 ~ a n a_{n} an 中,取决于前面 k 个数字。因此,等价于 A ( n − j , k ) A(n-j,k) A(nj,k) 这么一个问题。方案数为 f ( n − j ) f(n-j) f(nj)
  • 因此 f ( i ) = ∑ j = 1 k C i − 1 j − 1 ∗ ( j − 1 ) ! ∗ f ( i − j ) = ( i − 1 ) ! ∑ j = 1 k f ( i − j ) ( i − j ) ! f(i) = \sum\limits_{j=1}^{k}C_{i-1}^{j-1}*(j-1)!*f(i-j) = (i-1)!\sum\limits_{j=1}^{k}\frac{f(i-j)}{(i-j)!} f(i)=j=1kCi1j1(j1)!f(ij)=(i1)!j=1k(ij)!f(ij) (很简单的化简)
  • 整理一下: f ( i ) i ! = 1 i ∑ j = 1 m i n ( i , k ) f ( i − j ) ( i − j ) ! \frac{f(i)}{i!} = \frac{1}{i}\sum\limits_{j=1}^{min(i, k)}\frac{f(i-j)}{(i-j)!} i!f(i)=i1j=1min(i,k)(ij)!f(ij).
  • 所以,我们可以直接求 f ( i ) i ! \frac{f(i)}{i!} i!f(i),然后最后答案乘 i ! i! i! 就行。
  • 两个细节。一个是要求 f ( i ) i ! \frac{f(i)}{i!} i!f(i) 的前缀和。另一个是,求 1 ~ n 的逆元,这个要用线性的方法。n = 1e7的情况下, O ( n l o g n ) O(nlogn) O(nlogn) 会超时。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 10000010;
typedef long long ll;
const ll mod = 998244353;
ll f_i[maxn], fact[maxn], inv[maxn], sumf[maxn];
//inv 表示 1~n 的逆元

int main() {
    
    
	ll N, K;
	scanf("%lld%lld", &N, &K);
	fact[0] = inv[1] = 1;
	for (ll i = 1; i <= N; i++) {
    
    
		fact[i] = fact[i - 1] * i % mod;
	}

	for (ll i = 2; i <= N; i++) {
    
    
		inv[i] = (ll)(mod - mod / i) * inv[mod % i] % mod;
	}

	for (int i = 1; i <= K; i++) {
    
    
		f_i[i] = 1;
		sumf[i] = (f_i[i] + sumf[i - 1]) % mod;
	}
	for (int i = K + 1; i <= N; i++) {
    
    
		ll pre = (sumf[i - 1] - sumf[i - K - 1] + mod) % mod;
		f_i[i] = inv[i] * pre % mod;
		sumf[i] = (f_i[i] + sumf[i - 1]) % mod;
	}
	printf("%lld\n", f_i[N] * fact[N] % mod);
	return 0;
}

D. Rice Arrangement

  • 一个长度为n的环,有些位置有人,有些位置有菜。你可以旋转菜盘,使得所有菜位置+1或者-1,一个人可以吃一盘菜。求最短旋转次数使得所有人吃到菜。
  • 确定了第一个人吃的哪个菜以后,后面的人都是顺序吃菜了.
  • 先枚举第一个人要吃哪个菜,将每个人需要的顺时针旋转距离排个序,要么是最大的顺时针距离,要么是n-最小顺时针距离。或者相邻两个顺时针距离,一个顺时针跑,再逆时针跑另外一个。对于第三种情况,可以看注释
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1010;
int N, K, a[maxn], b[maxn], c[maxn];
typedef long long ll;
ll ans;
void cal(int d) {
    
    
	//先按照顺时针旋转计算答案
	for (int i = 0; i < K; i++) {
    
    
		c[i] = (a[i] - b[(i + d) % K] + N) % N;
	}
	
	//printf("*** %lld\n", ans);
	sort(c, c + K);
	//顺时针旋转最大值
	ans = min(ans, (ll)c[K - 1]);
	//逆时针旋转最大值
	ans = min(ans, (ll)N - c[0]);
	
	for (int i = 0; i < K - 1; i++) {
    
    
		/*
		这个是第三种情况,先顺时针再逆时针(或者先逆时针再顺时针)
		可以设想,c数组里面的元素,一定是有一个分界,前面部分是顺时针旋转距离较近,后面部分是逆时针旋转较近。
		而且,在后面部分的那些元素中,越往前的元素,逆时针所需要的距离越远。
		因此,先顺时针旋转到第一部分最后一个元素,再旋转到初始位置,再旋转到后面部分第一个元素。
		顺势针旋转到第一部分最后一个元素时,第一部分所有元素都已旋转到;
		而后面部分的第一个元素用逆时针旋转到的时候,后面所有元素均已旋转到;
		ans的改变就是在分界线改变的。
		*/
		ans = min(ans, 2LL * c[i] + (N - c[i + 1]));
	}
	for (int i = K - 1; i > 0; i--) {
    
    
		//先逆时针再顺时针
		ans = min(ans, 2LL * (N - c[i]) + c[i - 1]);
	}
}
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		scanf("%d%d", &N, &K);
		ans = 1e9;
		for (int i = 0; i < K; i++) {
    
    
			scanf("%d", &a[i]);
		}
		for (int i = 0; i < K; i++) {
    
    
			scanf("%d", &b[i]);
		}
		sort(a, a + K);
		sort(b, b + K);
		for (int i = 0; i < K; i++) {
    
    
			cal(i);
		}
		printf("%lld\n", ans);
	}
	return 0;
}

E. Sky Garden

  • 有n个圆他们的圆心都是(0,0),并且第i个圆的半径是i。然后有m条直线将这些圆等分成了2*m分。并且产生了一些交点。让你求任意两个不同的交点之间的最短距离之和是多少。
  • 这个题分类讨论,其实会讨论出很简单的结果。第一种情况是从原点到其他点(要特判 m = 1)的情况。第二种情况是 θ < 2 \theta < 2 θ<2 时,就是先往小圆走,再走圆弧;第三种情况时 θ > = 2 \theta >= 2 θ>=2 时,走两个半径即可。
  • 第二种情况不好讨论,其实想通了之后很简单。只计算往半径小的方向走的圆。分成三段:往里走 r i r_i ri,减去小圆直径 r 1 r_1 r1,加上圆弧长度。分别计算加了多少次,会更方便。
  • 这个题的精度说的是相对误差不大于 1 0 − 6 10^{-6} 106. double 整数部分位数多的时候误差很大,小数点往后数几位就不能保证了。
  • 总之,这个题细节极多。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const double PI = acos(-1);
int N, M;
int main() {
    
    
	scanf("%d%d", &N, &M);
	//半个圆周上,theta >= 2点的为x2, < 2的点为x1
	int x1 = (PI - 2.0) * M / PI + 1, x2 = M - x1;
	//printf("*** %d %d\n", x1, x2);
	double res1 = 0, res2 = 0, res3 = 0;
	//原点到圆上的点
	if(M != 1) res1 = 2.0 * M * N * (N + 1.0) / 2;
	//theta >= 2 的点
	res2 = 2.0 * (double)M * (double)N * (2.0 * x1 - 1) * (double)N * ((double)N + 1.0) / 2;
	for (int i = 1; i <= N; i++) {
    
    
		double M_ = M, x2_ = x2, i_ = i;
		// theta < 2 的点
		res3 += 2 * M_ * (PI * x2_ * (x2_ + 1.0) * i_ * (i_ - 1) / 2 / M_ + ((i_ - 1) * i_ * (2 * x2_ + 1)) - (2 * x2_ + 1) * i_ * (i_ - 1) / 2);
		res3 += PI * i * (x2_ + 1) * x2_;
	}
	//printf("### %f %f %f\n", res1, res2, res3);
	double ans = res1 + res2 + res3;
	printf("%.10f\n", ans);
	return 0;
}

F. Traveling in the Grid World

  • 题意:给定一个 n × m n×m n×m 的网格图,你站在起始点 ( 0 , 0 ) (0,0) (0,0),每次移动可以选择一个点 ( x , y ) (x,y) (x,y) 走线段到达它且这条线段不能经过其他格点,可以做任意次移动,问到达 ( n , m ) (n,m) (n,m) 所需的移动距离总和最小为多少?
  • 一个简单的结论:若 g c d ( n , m ) = 1 gcd(n,m)=1 gcd(n,m)=1,可以直接从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , m ) (n,m) (n,m),否则只需要一次转折即可到达 ( n , m ) (n,m) (n,m),而且这个转折点就在对角线附近。假设转折点为 ( x , y ) (x,y) (x,y)那么需要满足 g c d ( x , y ) = 1 gcd(x,y)=1 gcd(x,y)=1 g c d ( n − x , m − y ) = 1 gcd(n−x,m−y)=1 gcd(nx,my)=1。暴力枚举 ( 0 , 0 ) (0,0) (0,0) ( n , m ) (n,m) (n,m)连线附近的点作为转折点计算距离取最小值即可。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const double INF = 1e9;
ll N, M;
ll gcd(ll a, ll b) {
    
    
	if (b == 0) return a;
	return gcd(b, a % b);
}
double dist(ll x1, ll y1, ll x2, ll y2) {
    
    
	int dx = x1 - x2, dy = y1 - y2;
	return sqrt(pow(dx, 2) + pow(dy, 2));
}
double cal(ll x, ll y) {
    
    
	if (gcd(x, y) != 1 || gcd(M - x, N - y) != 1) return INF;
	if (x * (y - N) == (x - M) * y) return INF;
	return dist(0, 0, x, y) + dist(x, y, M, N);
}
int main() {
    
    
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		scanf("%lld%lld", &N, &M);
		double ans = INF;
		if (gcd(M, N) == 1) {
    
    
			printf("%.15f\n", dist(0, 0, M, N));
			continue;
		}
		for (ll x = 0; x <= M; x++) {
    
    
			ll y = N * x / M;
			ans = min(ans, cal(x, y));
			ans = min(ans, cal(x, y + 1));
			ans = min(ans, cal(x, y - 1));
		}
		printf("%.15f\n", ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45812711/article/details/113943135