前言
XY六题刚去,吴老师又发题下来了。不过幸好不是愚蠢的OI赛制,个人感觉还算可以。
A Spheres CodeChef - KSPHERES
题目描述
给你一些半球,有 上半球和 下半球。你可以选择半径相同的一对上下半球组成一个球。一个半径严格小于另一个半径的球可以套在大球中。我们称一个有 个球嵌套起来排成的序列为 序列。求对于 ,不同序列的数量。如果两个序列在同样位置上的两个球不同,那么这两个序列不同。如果构成两个球的上半球或下半球不同,那么这两个球不同。答案对 取模。
约定
- ,其中 表示球的半径
分析
我们可以用一个桶排先求出组成一个半径的球的方案数。我们设对于第 种半径的球,组成它的方案数为 。我们令 表示我们在前 种球中,选出一个 序列的方案数,则 ,接下来我们就可以很轻松的求解了。
参考程序
注意:本题用桶排即可,简单不易错。
// vjudge 244023 A
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int MAXN = 100005;
const int MAXC = 1005;
const int MOD = 1000000007;
int N, M, C, bu[MAXN], bl[MAXN], DP[MAXC][MAXC], B[MAXC], totb = 0;
int main() {
scanf("%d%d%d", &N, &M, &C);
int i, Ui, Li;
for (i = 0; i < N; i++) {
scanf("%d", &Ui);
bu[Ui]++;
}
for (i = 0; i < M; i++) {
scanf("%d", &Li);
bl[Li]++;
}
int tmp, j;
for (i = 1; i <= 1000; i++) {
tmp = (LL)bu[i] * bl[i] % MOD;
if (tmp) B[++totb] = tmp;
}
DP[0][0] = 1;
for (i = 1; i <= totb || i <= C; ++i)
for (DP[i][0] = j = 1; j <= i; j++) DP[i][j] = ((LL)DP[i - 1][j - 1] * B[i] % MOD + DP[i - 1][j]) % MOD;
for (i = 1; i <= C; ++i) printf("%d ", DP[totb][i + 1]);
putchar('\n');
return 0;
}
B Sereja and Commands CodeChef - SEACO
题目描述
Sereja有一个长度为
的序列
。初始时所有
。
Sereja在纸上写下了
个操作,编号为
~
。共有两类操作:
- ):将下标在 内的元素的值加1;
- ):执行编号在 内的所有操作,保证 小于当前操作的编号。
请帮 Sereja 执行所有操作。
最终序列元素对
取模。
约定
分析
乍一看这是一道线段树的题,但是我们并不要求在线查询,所以用差分即可。但是这个2号操作比较繁杂,如果直接模拟复杂度是不可想象的。但既然我们已经对数组用上了差分,为何不对操作也用一下差分呢?我们如果从前向后进行操作,那么当进行到2操作的时候,我们并不方便处理它要操作的操作区间;但若我们从后向前扫描这些操作,把差分的过程逆转过来,我们就可以统计出当前操作要被进行几次了,这样就成功转化了这个问题。
参考程序
// vjudge 244023 B
#include <cstdio>
#include <cstring>
const int MAXN = 100005;
const int MOD = 1000000007;
struct Opt {
int t, l, r;
} O[MAXN];
int N, M, T, dop[MAXN], da[MAXN];
inline void plus(int & x, int dl) { x += dl; if (x >= MOD) x -= MOD; }
inline void subtrc(int & x, int dl) { x -= dl; if (x < 0) x += MOD; }
void solve();
int main() {
scanf("%d", &T);
while (T--) solve();
return 0;
}
void solve() {
scanf("%d%d", &N, &M);
// 初始化差分数组,dop是操作的差分数组,da是序列的差分数组
memset(dop, 0, sizeof(int) * (M + 2));
memset(da, 0, sizeof(int) * (N + 2));
int i;
for (i = 1; i <= M; ++i) scanf("%d%d%d", &O[i].t, &O[i].l, &O[i].r);
int t = 1;
for (i = M; i > 0; --i) {
plus(t, dop[i]);
// 因为我们是倒着来的,所以堆操作的差分也要反过来做,这个很好理解
if (O[i].t == 2) subtrc(dop[O[i].l - 1], t), plus(dop[O[i].r], t);
else plus(da[O[i].l], t), subtrc(da[O[i].r + 1], t);
}
int Ai = 0;
for (i = 1; i <= N; i++) {
plus(Ai, da[i]);
printf("%d ", Ai);
}
putchar('\n');
}
C Blocked websites CodeChef - WSITES01
这篇题解独立出来写。
传送门
D Cooking Schedule CodeChef - SCHEDULE
题目描述
大厨是个名扬四海的大厨,所有人都想吃他做的菜。
如你所知,做菜并不是件简单的事情,大厨每天都得做菜,累到不行。因此,大厨决定给自己放几天假。
大厨为接下来的
天定制了一个计划。在第
天,如果
,那么大厨会在那天烹饪一道美味佳肴;如果
,那么大厨就会休息。
在大厨定下计划之后,他意识到他的计划并不完美:有些连着的日子里,大厨天天都得做菜,还是会很累;同时,也有时候大厨会连着休息好几天,这几天什么都不干,大厨也会觉得无聊。
因此大厨决定对这份计划做一些修改,但他也不想改太多地方,因此他决定最多修改
天的安排。大厨会选出最多
天,对于选出的每一天
,如果
,则将其改成 0;否则将其改成 1。
请你帮大厨写一个程序以决定修改哪些天的安排。修改之后应当保证,具有相同安排(即
相等)的连续一段的日子天数最少。
约定
- 输入中每组数据的 之和
分析
我们考虑动态规划,但是显然并不好进行转移。注意到题目要求是求最大值最小,那么我们考虑二分。那我们如何检查答案呢?我们可以预处理出原序列中连续的安排相同的天数,对于一个二分出来的天数 ,我们对于原来序列中的每一块,每个 个改变那天的安排即可。(若要改变那一段最后一天则改变倒数第二天,并且对于 时进行特判)这样就解决了这个问题。
参考程序
// vjudge 244023 D
#include <cstdio>
#include <algorithm>
const int MAXN = 1000005;
int N, K, A[MAXN], M;
char input[MAXN];
void solve();
bool check(int lim);
bool check1(); // check1()是对1的情况特殊判断
int main() {
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
void solve() {
scanf("%d%d%s", &N, &K, input);
char ch = input[0];
int i, len = 1, lb = 1, ub = 0;
// 分块
for (i = 1, M = 0; i < N; i++)
if (input[i] == ch) ++len;
else A[M++] = len, ub = std::max(ub, len), ch = input[i], len = 1;
A[M++] = len, ub = std::max(ub, len);
if (check1()) { puts("1"); return; }
int mid;
// 二分,当前二分区间为(lb, ub]
while (ub - lb > 1) {
mid = lb + ub >> 1;
if (check(mid)) ub = mid;
else lb = mid;
}
printf("%d\n", ub);
}
bool check(int lim) {
int res = 0;
++lim;
for (int i = 0; i < M; ++i) res += A[i] / lim;
return res <= K;
}
bool check1() {
int cnt = 0;
for (int i = 0; i < N; i++) cnt += input[i] ^ '0' ^ (i & 1);
return cnt <= K || (N - cnt) <= K;
}
E Flooring CodeChef - FLOORI4
题目描述
计算
答案对 取模。
分析
首先我们要明白一个事情:
这个结论是很好得出来的。
有了这个结论,我们再来考虑 ,它的可能的值是 的。我们可以分块处理,对于 一样的数一起计算,这样就省去很多冗余。
当然还有取模问题,由于模数不一定是个质数,我们不能乘上30的逆元。那么我们要用到如下结论:
证明如下:
我们设 ,则有 ,那么 。证毕。
那么我们在求四次方和的时候模数取 即可,最后返回和再除以30。
参考程序
代码十分短小。
// vjudge 244023 E
#include <cstdio>
typedef long long LL;
LL MOD, M, N;
inline LL sum(LL n) { return n % MOD * (n % MOD + 1) % MOD * (2 * n % MOD + 1) % MOD * (3 * n % MOD * n % MOD + 3 * n % MOD - 1) % MOD / 30; }
inline void plus(LL & x, LL d) { x = (x + d + M) % M; }
void solve();
int main() {
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
void solve() {
scanf("%lld%lld", &N, &M);
MOD = M * 30;
LL i, res = 0;
// 注意外面运算时取模仍是M(一开始因为这个WA了好久),然后可能减法后有负数所以再加上M。
for (i = 1; i <= N; i = N / (N / i) + 1) plus(res, (N / i) % M * (sum(N / (N / i)) - sum(i - 1) + M) % M);
printf("%lld\n", res);
}
F Permutation HDU - 4917
这篇题解独立出来写。
传送门
总结
其实这次的题目难度并不很大,但是由于基础仍然不太扎实,经验不够丰富,所以成绩并不理想。基础还需要更加加强!