CCPC 2020 ChangChun J 与 L

13 篇文章 0 订阅

2020CCPC长春J

题意:
给定一个区间 [ 0 , n ] [0,n] [0,n] ,区间内由一些圆,圆的半径和圆心都是整数,且圆上所有点都在区间内,每对圆的交点个数不超过 1 1 1 个,圆的半径不超过 5 5 5 ,求有多少种合法的方法加入新的圆。

思考:
这道题,一开始看着就觉得是个区间 dp ,但是又感觉不是那么好处理。但其实这道题就是一个最基础的区间 dp 题。
这道题的答案满足区间 dp 的一个普遍特征,最后由一个个大圆不相交的构成。
借助这道题,来总结一下区间 dp 的一般做法:

  1. 区间 dp 一般是按照区间长度从小到大进行合并。
  2. 去除掉非法区间后,考虑一个区间 [ l , r ] [l,r] [l,r] 的答案该怎么统计,一类是由 [ l , r − 1 ] [l,r-1] [l,r1] 或者 [ l + 1 , r ] [l+1,r] [l+1,r] 这种区间不进行任何操作转移过来,另一类是由两个区间分别操作然后再合并。

在这道题中,我们观察一下性质:
首先,只有不超过一个交点等价于不能相交。
然后,按照上面的一般做法,从小到大依次枚举区间长度。
f [ l ] [ r ] f[l][r] f[l][r] 表示区间 [ l , r ] [l,r] [l,r] 合法的方案数, g [ l ] [ r ] g[l][r] g[l][r] 表示区间 [ l , r ] [l,r] [l,r] 在存在外接圆的情况下的方案数。
那么, f [ l ] [ r ] f[l][r] f[l][r] 该怎么转移呢?考虑上面的一般做法第二条,可以直接由 f [ l ] [ r − 1 ] f[l][r-1] f[l][r1] f [ l + 1 ] [ r ] f[l+1][r] f[l+1][r] 转移过来,但是这样会有重复,中间部分会被统计两次,需要容斥掉,还需要加上包含整个区间的圆,比较麻烦。
我们不妨换个思路,考虑只由 f [ l , r − 1 ] f[l,r-1] f[l,r1] 转移过来,那么还少了部分是包含点 r r r 的圆,这个怎么统计呢?可以直接枚举以点 r r r 为右端点的圆的半径。然后由 [ l , p 0 ] [l, p0] [l,p0] [ p 0 , r ] [p0, r] [p0,r] 两个区间合并起来转移过来。
最后,再考虑覆盖整个区间的圆是否已经存在。

题解:
先预处理出来,哪些区间已经存在圆了,哪些区间不能有圆覆盖。由于半径的大小只有 5 5 5 ,这部分可以直接暴力预处理。
直接枚举区间长度,考虑区间 [ l , r ] [l,r] [l,r] 的转移。
f [ l , r ] = f [ l ] [ r − 1 ] + ∑ p 0 f [ l ] [ p 0 ] ∗ g [ p 0 ] [ r ] f[l,r] = f[l][r-1] + \sum_{p_0}{f[l][p0] * g[p0][r]} f[l,r]=f[l][r1]+p0f[l][p0]g[p0][r]
g [ l , r ] = g [ l ] [ r − 1 ] + ∑ p 0 f [ l ] [ p 0 ] ∗ g [ p 0 ] [ r ] g[l,r] = g[l][r-1] + \sum_{p_0}{f[l][p0] * g[p0][r]} g[l,r]=g[l][r1]+p0f[l][p0]g[p0][r] ( r − l ) m o d    2 = 0 (r-l)\mod2=0 (rl)mod2=0
最后,如果 [ l , r ] [l,r] [l,r] 不存在初始的圆覆盖,那么可以存在 [ l , r ] [l,r] [l,r] 的圆,也可以不存在,即 f [ l ] [ r ] f[l][r] f[l][r] 答案要翻倍。不过这里要注意区间长度不能超过 10 10 10

#include<bits/stdc++.h>
#define For(aa, bb, cc) for(int aa = (bb); aa <= (int)(cc); ++aa)
using namespace std;
typedef long long LL;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
int n, k;
int f[maxn][maxn], g[maxn][maxn];
// [l, r] : ans, cover circle
bool cov[maxn][maxn], ban[maxn][maxn];

LL fadd(LL x, LL y) {
	return x + y > mod ? x + y - mod : x + y;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cin >> n >> k;
	for(int i = 1; i <= k; ++i) {
		int c, r;
		cin >> c >> r;
		cov[c - r][c + r] = 1;
		for(int j = 0; j < c - r; ++j) {
			for(int l = c - r + 1; l < c + r; ++l) {
				ban[j][l] = 1;
			}
		}
		for(int j = c - r + 1; j < c + r; ++j) {
			for(int l = c + r + 1; l <= n; ++l) {
				ban[j][l] = 1;
			}
		}
	}
	for(int i = 0; i + 1 <= n; ++i) f[i][i + 1] = 1;
	for(int i = 0; i + 2 <= n; ++i) {
		if(ban[i][i + 2]) continue;
		if(cov[i][i + 2]) f[i][i + 2] = 1;
		else f[i][i + 2] = 2;
		g[i][i + 2] = 1;
	}
	for(int d = 3; d <= n; ++d) {
		for(int l = 0; l + d <= n; ++l) {
			int r = l + d;
			if(ban[l][r]) continue;
			f[l][r] = f[l][r - 1];
			if((r - l) % 2 == 0) g[l][r] = f[l][r - 1];
			for(int i = r - 2; i >= max(r - 10, l + 1); i -= 2) {
				f[l][r] = fadd(f[l][r], 1ll * f[l][i] * g[i][r] % mod);
				if((r - l) % 2 == 0) {
					g[l][r] = fadd(g[l][r], 1ll * f[l][i] * g[i][r] % mod);
				}
			}
			if(!cov[l][r] && (r - l) % 2 == 0 && (r - l) <= 10) {
				f[l][r] = 2 * f[l][r] % mod;
			}
		}
	}
	/*
	For(l, 0, n) {
		For(r, l + 2, n) {
			cout<<l<<" "<<r<<" "<<f[l][r]<<" "<<g[l][r]<<endl;
		}
	}
	*/
	printf("%d\n", f[0][n]);
	return 0;
}

2020CCPC长春L

题意:
构造一个数列 a n {a_n} an ,使得其满足:

  1. a i > = 0 a_i >= 0 ai>=0
  2. ∑ i a i = s \sum_{i}{a_i} = s iai=s
  3. i > 1 i > 1 i>1 ,满足 a i − a i + 1 = k a_i - a_{i+1} = k aiai+1=k 或者 a i + 1 − a i = 1 a_{i+1}-a_i =1 ai+1ai=1

思考:
看到这种题的第一反应,就是首先先排除掉一个条件,构造出一个序列,然后再用第二个条件进行调整。
(理论上的第一反应应该是,构造出最小的或者最大的
然后,我在考场上的时候,先排除掉 a i − a i + 1 = k a_i - a_{i+1} = k aiai+1=k ,然后构造了一个等差数列,发现每次用一次这个条件,就会使后面所有的权值减少 k + 1 k+1 k+1 ,那么可以得到以下不定方程:

n ∗ a 0 + n ∗ ( n − 1 ) 2 − ( k + 1 ) ∗ y = s n * a_0 + \frac{n*(n-1)}{2} - (k+1)*y = s na0+2n(n1)(k+1)y=s

这里 y ∈ [ 0 , n ∗ ( n − 1 ) 2 ] y \in[0, \frac{n*(n-1)}{2}] y[0,2n(n1)] ,然后可以调整的有 a 0 a_0 a0 y y y ,也就是一个标准的不定方程。移一下项,可以发现:
a 0 = s − n ∗ ( n − 1 ) 2 + ( k + 1 ) ∗ y n a_0 = \frac{s- \frac{n*(n-1)}{2} + (k+1)*y}{n} a0=ns2n(n1)+(k+1)y
y y y 的增大, a 0 a_0 a0 也在增大,但是由于后面的每个都要减去 k + 1 k+1 k+1 ,导致 a i > = 0 a_i >=0 ai>=0 不能得到很好的保证。(这一点直接观察不定方程也能看出来)

and then? 陷入瓶颈了。

那就换个构造方法,如果这样减法构造不好保证 a i > = 0 a_i >= 0 ai>=0,那么不如考虑一下加法构造。(这也是值最小的构造方案)
先按照下面方法构造一个序列:
0 , 1 , . . . , k , 0 , 1 , . . . , k , . . . 0, 1, ... , k, 0, 1, ... , k, ... 0,1,...,k,0,1,...,k,...
可以按照顺序依次对除了值为 k k k 的点进行增加 ( k + 1 ) (k + 1) (k+1) 的操作。如果值为 k k k 的点两侧的点都已经增加过了,那么它也可以增加。
假设上面的值求和为 y y y ,如果 ( s − y ) m o d    ( k + 1 ) = 0 (s-y) \mod (k+1) = 0 (sy)mod(k+1)=0 ,则原来的有解,然后再一个一个去放置,就可以构造出一组解。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值