Educational Codeforces Round 142

写在前面

比赛地址:https://codeforces.com/contest/1792

我是超级大鸽子咕咕咕

A

当且仅当有两个怪物初始血量为 1 时使用操作 1,否则用操作 2。

复制复制
//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
//=============================================================
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
int T = read();
while (T --) {
int n = read();
int num1 = 0, num2 = 0;
for (int i = 1; i <= n; ++ i) {
int h = read();
num1 += (h == 1);
num2 += (h != 1);
}
int ans = num1 / 2;
ans += num2 + (num1 % 2 == 1);
printf("%d\n", ans);
}
return 0;
}

B

先把操作 1 全用了。若此时心情大于 1,则再交替使用操作 2、3,直至不能使得心情值保持不变。此时两人心情相同,且进行操作一定会使某人的心情不可逆地降低,此时至多可再进行心情 +1 次操作。

用完操作 1 后,两人的心情值之和无法增加。则应保证在心情值不变的情况下尽可能地多进行操作,并在心情值不得不下降时,使心情值较小的一方的心情尽可能大。则显然按上述的顺序进行操作是最优的。

//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
//=============================================================
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
int T = read();
while (T --) {
int a[5] = {0}, ans, now;
for (int i = 1; i <= 4; ++ i) a[i] = read();
now = ans = a[1];
int maxa = std::max(a[2], a[3]), mina = std::min(a[2], a[3]);
if (now == 0) {
if (a[2] || a[3] || a[4]) ++ ans;
printf("%d\n", ans);
continue;
}
ans += 2 * mina;
maxa -= mina;
if (now < maxa) {
ans += now + 1;
printf("%d\n", ans);
continue;
}
ans += maxa;
now -= maxa;
if (now < a[4]) {
ans += now + 1;
printf("%d\n", ans);
continue;
}
ans += a[4];
printf("%d\n", ans);
}
return 0;
}

C

先手玩几组数据找找结论。以下将对元素 x,y 的一次操作简写为 (x,y),将元素 i 位于位置 i 称为 i 在有序位置。

  1. 一种通用的操作顺序是:

(n2,nn2+1)(n21,nn2+2)(1,n)

  1. 当 1 或 n 不在有序位置时,必须在最后进行 (1,n) 使得它们有序。
  2. 对结论 1 进行扩展,当有任意的数不在有序位置时,都必须在最后进行 (1,n)。因为其他的操作后 1n 一定在无序位置。 同理,当有除 1,n 的数不在有序位置时,倒数第二次操作一定是 (2,n1);当有除 1,2,n1,n 的数不在有序位置时,倒数第三次操作一定是 (3,n2)……
  3. 综合结论 1、3 考虑,我们仅需找到结论 1 中操作序列中尽量靠后的一个位置,使得这个位置之前的操作对象们都是相对有序的。从该位置开始向后进行操作即可。

实现时记录每个数在排列中的位置,按照数的大小中间向两侧比较位置,检查是否相对有序即可。复杂度 O(n) 级别。

//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
const int kN = 2e5 + 10;
//=============================================================
int n, a[kN], pos[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
int T = read();
while (T --) {
n = read();
for (int i = 1; i <= n; ++ i) {
a[i] = read();
pos[a[i]] = i;
}
int lth = 0;
int i, j;
if (n % 2) {
i = j = (n + 1) / 2;
} else {
i = n / 2, j = n / 2 + 1;
}
for (int fir = n, las = 1;
i > 0 && j <= n; -- i, ++ j) {
if (pos[i] <= pos[j] && pos[i] <= fir && pos[j] >= las) {
if (i != j) ++ lth;
} else {
break;
}
fir = pos[i], las = pos[j];
}
printf("%d\n", n / 2 - lth);
}
return 0;
}

D

对于两个长度为 m 的排列 p,q,定义一种运算 pq=r。运算结果 r 也是一个长度为 m 的排列,且满足:ri=qpi。对于排列 p 定义其魅力值为满足 p1=1,p2=2,,pk=k 的最大的下标 k。特别地,若 p11p 的魅力值为 0。
t 组数据,每组数据给定 n 个长度为 m 的排列 a1an。对于所有 ai(i[1,n]),求 aiaj(j[1,n]) 的魅力值的最大值。
1t1041n5×1041m10n5×104
2S,256MB。

凭什么卡我 bitset 暴力啊(恼

发现定义的这个运算和矩阵乘法相当相似,不满足交换律,且满足结合律,证明可自己手玩。于是考虑矩阵乘法那一套:定义单位排列 A=(1,2,,m);对于排列 p,将满足 pq=A 的排列 q 定义为排列 p逆排列,记作 p1

再回到题目所求,若 pq=(1,2,,k,) 的魅力度为 k,考虑变换一下:

pqq1=(1,2,,k,)q1pA=(1,2,,k,)q1

上式等价于 pq 的逆排列最长公共前缀为 k

综上,考虑求得排列 a1an 的逆排列,查询 ai 的答案时在所有逆排列上匹配最长公共前缀即可。使用 Trie 即可简单实现。总复杂度 O(nm) 级别。

或者更加具体地,还可以从实际意义考虑逆排列的含义:qi1 即排列 qi 的位置。如果从等号右边往左边理解,上式的另一种解释是处理出每个排列中 1m 的位置并按顺序组成一个新的排列 q1,求这个新的排列与所有原排列的最长公共前缀。

注意清空 Trie 时要全部清空,并且数组开大点。两个错误叠起来 RE 变 WA 真是把我鲨了。

//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
const int kN = 5e4 + 10;
const int kM = 11;
//=============================================================
int n, m, a[kN][kM], inv[kN][kM];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
namespace Trie {
const int kNode = kN << 3;
int num, tr[kNode][11];
void Init() {
for (int i = 0; i <= num; ++ i) {
memset(tr[i], 0, sizeof (tr[i]));
}
num = 0;
}
void Insert(int id_) {
int now = 0;
for (int i = 1; i <= m; ++ i) {
int x = inv[id_][i];
if (!tr[now][x]) tr[now][x] = ++ num;
now = tr[now][x];
}
}
int Query(int id_) {
int now = 0, ret = 0;
for (int i = 1; i <= m; ++ i) {
int x = a[id_][i];
if (!tr[now][x]) break;
++ ret;
now = tr[now][x];
}
return ret;
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
int T = read();
while (T --) {
n = read(), m = read();
Trie::Init();
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
a[i][j] = read();
inv[i][a[i][j]] = j;
}
Trie::Insert(i);
}
for (int i = 1; i <= n; ++ i) {
printf("%d ", Trie::Query(i));
}
printf("\n");
}
return 0;
}

E

t 组数据,每组数据给定参数 n,m1,m2。对于 m1×m2 的约数 d,记满足 1x,yn,d=x×y最小xxd,求上述 xd 的个数,并输出所有 xd 的异或和。
1t101n,m1,m2109
2.5S,256MB。

写了个 O(m) 的质因数分解我是超级大啥b。

前置知识:约数个数的上界估计:https://www.cnblogs.com/ubospica/p/10392523.htmhttps://blog.csdn.net/VFleaKing/article/details/88809335
省流版:1018 内的数约数至多有 103680105 个。

于是考虑先对 m1,m2 进行质因数分解,再暴力凑出 m1×m2 的所有约数,问题变为对每一个约数 d 求得一组满足上述条件的 (x,y),且满足 x 尽可能地小,则 y 应当是满足 yn 的最大的 d 的约数。如果我们可以求得每个约数 d 对应的 y,那么只需检测 x=dyn 是否成立即可判断 d 是否对答案有贡献。

直接暴力枚举 y 的复杂度是无法承受的。考虑子集 DP。记 fd 表示满足 ynd 的最大的约数。对于 dn,显然有 fd=d;对于 d>n,考虑枚举 d 的质因子 p,有:

fd=maxp|dfdp

m1×m2 的质因子的数量级只有 10 左右,DP 复杂度并不高。处理出 DP 值后累计有贡献的 fd 即可。总复杂度约为 O(t(m1+m2+10×d(m1×m2))) 级别。

DP 时要注意实现,虽然出题人没卡,但直接用 map 让 df 的下标跑的相当慢。应当令 d 的编号作 f 的下标,转移时使用二分查找 fdp 即可。

//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
#define LL long long
const int kN = 1e5 + 1e4 + 10;
//=============================================================
int n, m1, m2, sz, ans1, f[kN];
LL ans2;
std::vector <int> p, c;
std::vector <LL> d;
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
void Init() {
sz = ans1 = 0; ans2 = 0ll;
p.clear(), c.clear(), d.clear();
n = read(), m1 = read(), m2 = read();
int temp1 = m1, temp2 = m2;
for (int i = 2; i * i <= m1 || i * i <= m2; ++ i) {
if (temp1 % i == 0 || temp2 % i == 0) {
++ sz;
p.push_back(i), c.push_back(0);
while (temp1 % i == 0) temp1 /= i, c[sz - 1] ++;
while (temp2 % i == 0) temp2 /= i, c[sz - 1] ++;
}
}
if (temp1 > temp2) std::swap(temp1, temp2);
if (temp1 > 1) ++ sz, p.push_back(temp1), c.push_back(1);
if (temp2 > 1 && temp1 == temp2) ++ c[sz - 1];
if (temp2 > 1 && temp1 != temp2) ++ sz, p.push_back(temp2), c.push_back(1);
}
void Dfs(int lth_, LL d_) {
if (lth_ == sz) {
d.push_back(d_);
return ;
}
Dfs(lth_ + 1, d_);
LL x = 1ll;
for (int i = 1; i <= c[lth_]; ++ i) {
x *= p[lth_];
Dfs(lth_ + 1, d_ * x);
}
}
void DP(LL id_) {
LL d_ = d[id_];
if (d_ <= n) {
f[id_] = d_;
return ;
}
f[id_] = 0;
for (int i = 0; i < sz; ++ i) {
if (d_ % p[i]) continue;
for (int l = 0, r = id_ - 1; l <= r; ) {
int mid = (l + r) >> 1;
if (d[mid] == d_ / p[i]) {
f[id_] = std::max(f[id_], f[mid]);
break;
} else if (d[mid] < d_ / p[i]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
int t = read();
while (t --) {
Init();
Dfs(0, 1);
std::sort(d.begin(), d.end());
for (int i = 0, dnum = d.size(); i < dnum; ++ i) {
DP(i);
if (f[i] && d[i] / f[i] <= n) {
++ ans1, ans2 ^= d[i] / f[i];
}
}
printf("%d %lld\n", ans1, ans2);
}
}

F

一个感觉没那么牛逼的计数 DP,但是鸽了,有空再说。

写在最后

  • D 另一种理解中的的反向考虑。
  • 质因数分解是 O(m) 的。
  • 1018 内的数约数至多有 103680105 个。
  • 10^9 内的数质因子至多有 10 个(2×3×5×7×11×13×17×19×23×29=6,469,693,230)。

最近 CF 一场连着一场还要学车还要抽空学杀软文化课防止挂科……走一步看一步吧你妈的,今晚还有场 Div3,现在手里屯了三套题没补完,我真是超级大鸽王。

posted @   Luckyblock  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示