2019.10.12模拟赛
关于这次考试
什么神仙题啊上来大模拟???
T1 德州扑克
就是给你七张牌输出权值最大的牌。
大模拟就好了,注意细节,注意读题。
#include <bits/stdc++.h>
using namespace std;
namespace tx {
const int COLOR_MAX = 14;
const int HAVE_MAX = 7;
const int MAXN = 10;
char color[20];
int a[MAXN];
int have[20];
int ans[MAXN], tot;
inline void in(const int &x) { ans[++tot] = x; }
inline void out() {
assert(tot == 5);
sort(ans + 1, ans + 5 + 1, greater<int>());
for (register int i = 1; i <= 5; ++i) {
if (ans[i] == 10)
cout << 10 << " ";
else
cout << color[ans[i]] << " ";
}
return;
}
inline int main() {
for (register int i = 1; i <= 9; ++i) color[i] = i + '0';
color[11] = 'J';
color[12] = 'Q';
color[13] = 'K';
color[14] = 'A';
color[1] = 'A';
for (register int i = 1; i <= HAVE_MAX; ++i) {
char s[5];
cin >> s;
if (s[0] == 'A')
a[i] = 14;
else if (s[0] == 'K')
a[i] = 13;
else if (s[0] == 'Q')
a[i] = 12;
else if (s[0] == 'J')
a[i] = 11;
else if (s[0] == '1' && s[1] == '0')
a[i] = 10;
else
a[i] = s[0] - '0';
++have[a[i]];
if (a[i] == 14)
++have[1];
}
sort(a + 1, a + 1 + HAVE_MAX, greater<int>());
// judge 1
for (register int i = COLOR_MAX; i >= 2; --i) {
if (have[i] >= 4) {
for (register int j = 1; j <= 4; ++j) in(i);
for (register int j = 1; j <= HAVE_MAX; ++j) {
if (a[j] != i) {
in(a[j]);
break;
}
}
out();
return 1;
}
}
// judge2
register int bo3 = 0, bo2 = 0;
for (register int i = COLOR_MAX; i >= 2; --i) {
if (!bo3 && have[i] >= 3) {
bo3 = i;
continue;
}
if (!bo2 && have[i] >= 2) {
bo2 = i;
continue;
}
}
if (bo3 && bo2) {
for (register int i = 1; i <= 3; ++i) in(bo3);
for (register int i = 1; i <= 2; ++i) in(bo2);
out();
return 2;
}
// judge3
for (register int i = COLOR_MAX - 4; i >= 1; --i) {
register bool bo = 1;
for (register int j = 0; j <= 4; ++j) {
if (!have[i + j]) {
bo = 0;
break;
}
}
if (bo) {
for (register int j = 0; j <= 4; ++j) {
in(i + j);
}
out();
return 3;
}
}
// judge4
bo3 = bo2 = 0;
for (register int i = COLOR_MAX; i >= 2; --i) {
if (have[i] >= 3) {
bo3 = i;
break;
}
}
if (bo3) {
for (register int i = 1; i <= 3; ++i) {
in(bo3);
}
register int cnt = 0;
for (register int j = 1; j <= HAVE_MAX; ++j) {
if (a[j] != bo3) {
in(a[j]);
++cnt;
}
if (cnt >= 2)
break;
}
out();
return 4;
}
// judge5
bo3 = 0, bo2 = 0;
for (register int i = COLOR_MAX; i >= 2; --i) {
if (!bo3 && have[i] >= 2) {
bo3 = i;
continue;
}
if (!bo2 && have[i] >= 2) {
bo2 = i;
break;
}
}
if (bo3 && bo2) {
in(bo3), in(bo3);
in(bo2), in(bo2);
for (register int i = 1; i <= HAVE_MAX; ++i) {
if (a[i] != bo3 && a[i] != bo2) {
in(a[i]);
break;
}
}
out();
return 5;
}
// judge6
bo3 = bo2 = 0;
for (register int i = COLOR_MAX; i >= 2; --i) {
if (have[i] >= 2) {
bo3 = i;
break;
}
}
if (bo3) {
in(bo3), in(bo3);
register int cnt = 0;
for (register int i = 1; i <= HAVE_MAX; ++i) {
if (a[i] != bo3) {
in(a[i]);
++cnt;
}
if (cnt >= 3)
break;
}
out();
return 6;
}
// judge7
for (register int i = 1; i <= 5; ++i) in(a[i]);
out();
return 7;
return 0;
}
} // namespace tx
int main() {
#ifdef lky233
freopen("texas.in", "r", stdin);
freopen("texas.out", "w", stdout);
#endif
tx::main();
}
这道题由于情况很多,人工模拟又很容易出锅……hack起来很爽快。
至于maker,因为这道题的状态是有限的,可以枚举状态,maker就可以先读入seed来获知当前状态,接着求出下一个合法状态并更新seed。
这样的maker有几个好处:
不是rand而是枚举状态,不会遗漏。
可以手动更改seed以构造特殊数据。
但也有坏处:
枚举全部状态时间过长。
有很多相似的状态不会拍出错误,效率降低。
(找一个机房空机子挂上两天也是可以的)
#include <bits/stdc++.h>
using namespace std;
char color[100];
int seed[100];
int v[20];
inline void nex()
{
++seed[7];
for (register int i = 7; i >= 1; --i)
{
if (seed[i] > 13)
{
seed[i] = 1;
++seed[i - 1];
}
}
if(seed[1] == 14)
{
while(1)
;
cerr << "accept" << endl;
}
}
inline bool judge()
{
memset(v, 0, sizeof(v));
for (register int i = 1; i <= 7; ++i)
{
++v[seed[i]];
if (v[seed[i]] > 4)
return false;
}
return true;
}
int main()
{
freopen("seed.txt", "r", stdin);
// freopen("seed.txt", "w", stdout);
for (register int i = 1; i <= 9; ++i)
color[i] = i + '0';
color[11] = 'J';
color[12] = 'Q';
color[13] = 'K';
color[14] = 'A';
color[1] = 'A';
for (register int i = 1; i <= 7; ++i)
scanf("%d", &seed[i]);
nex();
while (!judge())
nex();
freopen("seed.txt", "w", stdout);
for (register int i = 1; i <= 7; ++i)
printf("%d ", seed[i]);
freopen("texas.in", "w", stdout);
for (register int i = 1; i <= 7; ++i)
{
if (seed[i] == 10)
printf("10\n"), cerr << 10 << " ";
else
printf("%c\n", color[seed[i]]), cerr << color[seed[i]] << " ";
}
cerr << endl;
}
T2 战斗
题面
秦师兄手下有𝑛个士兵,每个士兵有一个战斗力𝑎𝑖。他打算从中挑选出至少𝑘个士兵组成一
支部队,并在选出的士兵中挑选出一个作为队长。然而秦师兄并不关心部队的战斗力。他只希
望部队的混乱度最小。混乱度的定义为:所有士兵的战斗力与队长的战斗力之差的平方和。秦
师兄想知道最小的混乱度是多少。
第二行𝑛个不严格递增的正整数𝑎𝑖,表示𝑛个士兵的战斗力。
做法
首先\(\geq k\)个数可以转化为\(k\)个数,因为多一个数会导致值只增不减。并且取值是连续的,因为若取不连续的一段,一定有未取到的数使得答案更优。
其次,一段序列与x的差的平方和最小,x是平均数。
证明:
$ans = \sum\limits_{i = 1}^{i \leq len}(a_i - x)^2 $$= \sum\limits_{i = 1}^{i \leq len}a_{i}^2 - \sum\limits_{i = 1}^{i \leq len} {2 \cdot a_i \cdot x} + len \cdot x^2$
我们发现这是个二次函数,找到最小值:
\(x = \frac{\sum\limits_{i = 1}^{i \leq len} a_i}{len} = \bar a\)
这样我们可以二分查找区间内最接近\(\bar a\)的数,O(1)计算该区间的值。时间复杂度\(O(n \log n)\)。
poread(n), poread(k);
for (register int i = 1; i <= n; ++i) poread(a[i]);
for (register int i = 1; i <= n; ++i) q[i] = (long long)a[i] * a[i];
for (register int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + a[i];
for (register int i = 1; i <= n; ++i) sum_q[i] = sum_q[i - 1] + q[i];
for (l = 1; (r = l + k - 1) <= n; ++l) {
register long long mid = (sum[r] - sum[l - 1]) / (r - l + 1);
int mid1 = find1(mid), mid2 = find2(mid);
if (mid1 == mid2) {
ans = min(ans, calc(a[mid1]));
} else {
ans = min(ans, calc(a[mid1]));
ans = min(ans, calc(a[mid2]));
}
}
printf("%lld\n", ans);
但序列递增,所以决策点只会右移。所以可以\(O(n)\)扫描一遍得到答案。
int main() {
poread(n), poread(k);
for (register int i = 1; i <= n; ++i) poread(a[i]);
for (register int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + a[i];
for (register int i = 1; i <= n; ++i) sum_q[i] = sum_q[i - 1] + 1ll * a[i] * a[i];
long long mid = sum[k] / k;
register int point = find1(mid);
register long long tmp;
for (l = 1; (r = l + k - 1) <= n; ++l) {
while (calc(a[point + 1]) <= (tmp = calc(a[point]))) ++point;
ans = min(ans, tmp);
}
printf("%lld\n", ans);
}
T3 计数
题面
别看秦师兄平时一副热爱打游戏的样子,但在信息学计数题上的造诣可谓是颇深,你看在这 次模拟赛中,他就除了这样一道计数题。
秦师兄觉得图论很有趣。因此有一个𝑛个点的有向图,编号从 1 到𝑛,所有边的长度都是 1。
秦师兄还想融合一些数论元素。因此 i 向 j 连边当且仅当 i 整除 j。请注意一个正整数必定
整除它自身,因而秦师兄允许了自环的存在。
秦师兄觉得还不够好,因为所有的正整数都整除 0。于是她新建了一个编号为 0 的点,并
让 1−𝑛 的所有点都向 0 连一条长度为 1 的边。
秦师兄现在很满意。她写下了下面 4 个问题交给你来解决。
- 求出从 1 到𝑞1,且长度恰为 2 的路径个数。
- 求出从 1 到 0,且长度不大于𝑞2的路径个数。
- 求出从 1 到 0,没有经过重复的点,且长度不大于𝑞3的路径个数。
- 令一条路径的权值为它经过的所有的点的编号之和。求出 (问题 3.)中所有路径的权值
和。
所有问题的答案对 109 + 7 取模。秦师兄不希望你爆零,因此设置了部分分。请注意最后的部分分设定。
lemon没有部分分啊
int main()
{
cin >> n >> q1 >> q2 >> q3;
register int i = 1;
for (i = 1; i * i <= q1; ++i)
if (q1 % i == 0)
ans1 += 2;
--i;
if (i * i == q1)
--ans1;
if (q1 == 0)
ans1 = n;
int t = log2(n) + 1;
for (register int l = 1, d, r; l <= n; l = r + 1)
id[d = n / l] = ++tot, pos[tot] = d, r = n / d;
for (register int i = 1; i <= tot; ++i)
f[i][1] = g[i][1] = 1;
for (register int j = 1; j < t; ++j)
{
for (register int i = 1; i <= tot; ++i)
{
register int x = pos[i];
for (register int l = 2, d, r; l <= x; l = r + 1)
{
d = x / l, r = x / d;
register int len = r - l + 1;
f[i][j + 1] = (f[i][j + 1] + 1ll * len * f[id[d]][j] % MOD) % MOD;
g[i][j + 1] = (g[i][j + 1] + 1ll * len * (l + r) / 2 % MOD * g[id[d]][j] % MOD + 1ll * len * f[id[d]][j] % MOD) % MOD;
}
}
}
for (register int i = 1; i <= min(q3, t); ++i)
ans3 = (ans3 + f[1][i]) % MOD;
for (register int i = 1; i <= min(q3, t); ++i)
ans4 = (ans4 + g[1][i]) % MOD;
inv[1] = c[0] = 1;
for (register int i = 2; i <= t; ++i)
inv[i] = 1ll * (MOD - MOD / i) % MOD * inv[MOD % i] % MOD;
for (register int i = 1; i <= t; ++i)
c[i] = 1ll * c[i - 1] * inv[i] % MOD * (q2 - i + 1) % MOD;
for (register int i = 1; i <= t; ++i)
ans2 = (ans2 + 1ll * c[i] * f[1][i]) % MOD;
printf("%d %d %d %d", ans1, ans2, ans3, ans4);
return 0;
}