2021 BNU Winter Training 6 (2020 CCPC长春)

2021 BNU Winter Training 6 (The 14th Chinese Northeast Collegiate Programming Contest)

训练网址

A. Strange Memory

  • 启发式合并,挖坑。

B. Abstract Painting

  • 给定n,在x轴[0,n]范围内画半径不超过5的圆,圆心在x轴上,要求任意两个圆不相交(可以相切),且已有固定的k个圆,求满足条件的画法方案数。
  • f ( i , j , k ) f(i, j, k) f(i,j,k):k = 0 表示存在一个圆,左端点为i,右端点为j,在 [ i , j ] [i,j] [i,j] 这个范围内随便放圆的方案数;k = 1 表示不存在一个左端点为 i,右端点为 j,在 [ i , j ] [i,j] [i,j] 这个范围内随便放圆的方案数。注意这里只考虑 [ i , j ] [i,j] [i,j] 这个范围内的方案数,并不算在这个范围之外怎么放圆。
  • 那么, f ( i , j , 1 ) f(i, j, 1) f(i,j,1) 不为0只有一种情况,就是外边套圆,且 ( j − i ) ≤ 10 (j - i)\le10 (ji)10 ( j − i ) % 2 = = 0 (j - i) \% 2 == 0 (ji)%2==0,这时候 f ( i , j , 1 ) = f ( i , j , 0 ) f(i, j, 1) = f(i, j, 0) f(i,j,1)=f(i,j,0)
  • 接下来,讨论 f ( i , j , 0 ) f(i, j, 0) f(i,j,0),如果 i 这个点不放一个圆(左端点为 i),结果就是 f ( i + 1 , j , 0 ) + f ( i + 1 , j , 1 ) f(i + 1, j, 0) + f(i + 1, j, 1) f(i+1,j,0)+f(i+1,j,1);如果i 这个点不放一个圆(左端点为 i),设这个圆的右端点为 r,那么就是看作 f ( i , r , 1 ) ∗ [ f ( r , j , 1 ) + f ( r , j , 0 ) ] f(i, r, 1) * [f(r, j, 1) + f(r, j, 0)] f(i,r,1)[f(r,j,1)+f(r,j,0)].
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1010, maxk = 5010;
//typedef pair<int, int> P;
typedef long long ll;
const ll mod = 1e9 + 7;
//P a[maxk];
ll f[maxn][maxn][2];
//exist(i, j) 已经存在一个左端点为 i,右端点为 j 的圆;no(i, j) 表示不可以放一个左端点为 i,右端点为 j 的圆
bool exist[maxn][maxn], no[maxn][maxn];
int main() {
	int N, K;
	scanf("%d%d", &N, &K);
	for (int i = 0; i < K; i++) {
		int C, R;
		scanf("%d%d", &C, &R);
		//该圆的左右边界
		int LL = C - R, RR = C + R;
		exist[LL][RR] = true;
		//如果区间跨过该圆的一个边界,那么在这个区间内不能随便放圆
		for (int r = LL + 1; r <= RR - 1; r++) {
			for (int l = 0; l <= LL - 1; l++) no[l][r] = true;
		}
		for (int l = LL + 1; l <= RR - 1; l++) {
			for (int r = RR + 1; r <= N; r++) no[l][r] = true;
		}
	}
	for (int l = 0; l < N; l++) {
		f[l][l + 1][0] = 1;  //空白也算一种方案
		f[l][l + 1][1] = 0;  //不存在直径为1的圆
	}
	for (int len = 2; len <= N; len++) {
		for (int l = 0; l <= N - len; l++) {
			int r = l + len;
			//no[l][r] 表示区间 [l, r] 内不可以随便放圆
			if (no[l][r]) {
				f[l][r][0] = f[l][r][1] = 0;
				continue;
			}
			//l点不放圆
			f[l][r][0] = (f[l + 1][r][0] + f[l + 1][r][1]) % mod;
			//l点放圆
			for (int D = 2; D <= 10 && D < len; D += 2) {
				f[l][r][0] = (f[l][r][0] + f[l][l + D][1] * (f[l + D][r][0] + f[l + D][r][1])) % mod;
			}
			if ((r - l) % 2 == 0 && (r - l) <= 10) {
				f[l][r][1] = f[l][r][0];
			}
			if (exist[l][r]) f[l][r][0] = 0;
		}
	}
	ll ans = (f[0][N][0] + f[0][N][1]) % mod;
	printf("%lld\n", ans);
	return 0;
}

C. Ragdoll

  • 启发式合并,挖坑。

D. Coordinate Paper

  • 让你构造一个 n 的序列,要么 a i = a i − 1 + 1 a_i=a_{i-1}+1 ai=ai1+1。要么 a i + 1 = a i − k a_{i+1}=a_{i}-k ai+1=aik。和为 S S S
  • 我们想,确定了 a 1 a_1 a1 之后, a 2 a_2 a2要么是 a 1 + 1 a_1+1 a1+1,要么是 a 1 − k a_1-k a1k;这两种情况下, ( a 1 + a 2 ) % ( k + 1 ) (a_1+a_2) \% (k+1) (a1+a2)%(k+1) 的值都是相等的。因此我们可以得到:当 a 1 a_1 a1 确定,整个序列的和 s u m % ( k + 1 ) sum \%(k+1) sum%(k+1) 也就确定了。这样我们可以从 0 到 k 枚举 a 1 a_1 a1 的值,然后 a i + 1 = ( a i + 1 ) % ( k + 1 ) a_{i + 1} = (a_{i}+1)\%(k+1) ai+1=(ai+1)%(k+1) 这样构造整个序列。如果 s u m = = S % ( k + 1 ) sum == S\%(k+1) sum==S%(k+1) S > = s u m S >= sum S>=sum,那么这种方案就可以。如果没有方案满足要求就输出-1。
  • 接下来考虑如何将答案变大,我们可以先把 0 变成 k + 1 k+1 k+1;如果 0 用完了可以将 1 变成 k + 2 k+2 k+2 …由此得到最后的总和 S S S
  • 细节真多
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 100010;
typedef pair<ll, ll> P;
ll a[maxn];
int main() {
	ll N, K, S;
	scanf("%lld%lld%lld", &N, &K, &S);
	ll sum = 0;
	for (int i = 1; i <= N; i++) {
		a[i] = i % (K + 1);
		sum += a[i];
	}
	ll a1 = a[1], an = a[N];
	for (int i = 0; i <= K; i++) {
		if ((sum % (K + 1) == S % (K + 1)) && S >= sum) {
			vector<P> ids;  //记录每个数字出现的下标
			//不要忘记将此数组更新
			for (int j = 1; j <= N; j++) {
				a[j] = (a[j] + i) % (K + 1);
				ids.push_back({ a[j], j });
			}
			sort(ids.begin(), ids.end());
			ll u = (S - sum + (N * (K + 1) - 1)) / (N * (K + 1)), v = (S - sum) % (N * (K + 1)) / (K + 1);
			//printf("*** %lld %lld %lld %lld %lld\n", a1, an, sum, u, v);
			
			//细节,小心 u 可能等于0
			if (u != 0) {
				
				//如果 v 为 0 的话,试试 2 1 5 这个样例就明白了
				if (v == 0) u++;
				for (auto p : ids) {
					ll id = p.second;
					if (v) {
						a[id] += u * (K + 1);
						v--;
					}
					else {
						a[id] += (u - 1) * (K + 1);
					}
				}
			}
			for (int j = 1; j <= N; j++) {
				printf("%lld%c", a[j], j == N ? '\n' : ' ');
			}
			return 0;
		}
		sum = sum - a1 + (an + 1) % (K + 1);
		a1 = (a1 + 1) % (K + 1), an = (an + 1) % (K + 1);
	}
	printf("-1\n");
	return 0;	
}
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页