『模拟赛』多校A层冲刺NOIP2024模拟赛22(炼石计划20)
Rank
成炼史了
byd 半天截不上,rk151,0+20+0+32=52pts
A. 邻间的骰子之舞
签,赛时不知道为什么稀里糊涂就过去了。
性质推推就出来了。首先显然,一次复制之后必定跟至少一次粘贴。那么设一次 复制+粘贴 的操作总数为 \(a_i\),最终总长度即为 \(\prod_{i=1}^k\ a_i\),花费即为 \(xk+y\sum_{i=1}^k(a_i-1)=k(x-y)+y\sum_{i=1}^k\ a_i\)。
然后还没结束。我们取 \(\forall i,j\in[1,k],i\neq j\),那么如果 \(|a_i-a_j|\ge 2\),我们完全可以将它们变成 \(\max(a_i,a_j)-1,\min(a_i,a_j)+1\),花费不便,长度变长。那么得出第二个关键结论:任意两个 复制+粘贴 的操作总数相差不超过 1。
那么不妨将所有 \(a_i\) 用 \(t,t+1\) 表示。发现复制次数 \(k\) 一定是不超过 \(\log n\) 级别的,我们可以枚举 \(k\),然后枚举其中 \(t\) 的个数 \(p\),那么可以通过二分得到一个最小的合法 \(t\) 值,然后对所有这样的结果取 \(\min\) 即为最终答案。复杂度是 \(\mathcal{O(\log^3 n\times \log \log n)}\)。
注意二分 check 时小心快速幂炸 __int128!当值 \(\ge 10^{18}\) 时直接返回 true。当然朴素实现也没什么问题,上面复杂度也是按这样算的。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef __int128 lll;
typedef unsigned long long ull;
#define lx lll
inline lx qr()
{
char ch = getchar(); lx x = 0, f = 1;
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
return x * f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define ppp pair<pii, pii>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 5e4 + 5;
const int mod = 1e9 + 7;
lll n, x, y, ans = 1e36;
namespace Wisadel
{
void Wp(lll x, bool fi)
{
if(x == 0 && fi){putchar('0'); return ;}
if(!x) return ;
Wp(x / 10, 0);
putchar(x % 10 + '0');
}
bool Wck(lll t, lll p, lll k)
{
lll res = 1;
fo(i, 1, p)
{
res = res * t;
if(res > n) return 1;
}
fo(i, 1, k - p)
{
res = res * (t + 1);
if(res > n) return 1;
}
if(res > n) return 1;
return 0;
}
short main()
{
freopen("dice.in", "r", stdin), freopen("dice.out", "w", stdout);
n = qr, x = qr, y = qr;
ll zc = n;
int up = log2(zc) + 1;
fo(k, 1, up) fo(p, 0, k)
{
lll l = 1, r = 1e18;
while(l < r)
{
lll mid = (l + r) >> 1;
if(Wck(mid, p, k)) r = mid;
else l = mid + 1;
}
lll res = (x - y) * k + y * r * p + y * (r + 1) * (k - p);
ans = min(ans, res);
}
Wp(ans, 1); puts("");
return Ratio;
}
}
signed main(){return Wisadel::main();}
// Now there's only one thing I can do
// Fight until the end like I promised to
B. 星海浮沉录
套路题,因为之前见到直接润了所以完全不会。
转化题意,发现 mex 具有一定的单调性:设满足 mex 为 \(x\) 的最大长度为 \(l\),那么对于 \(\forall len\le l\) 的区间,一定可以让它不包含 \(x\)(这么说是因为 mex 可能比 \(x\) 小)。那么我们如果求出 \(\forall i\in[0,n]\) 所有最长不出现 \(i\) 区间的长度,我们就可以在其中找出满足 \(l_i\le x\) 的最小的 \(i\) 即为最小的 mex。
怎么处理呢,发现满足不包含 \(i\) 的最大区间一定是两个相邻的 \(i\) 的距离,每个值为 \(i\) 的下标显然只会有一个,我们可以用 set 存储,然后取所有差加入一个 multiset 中存储距离,它的 \(l\) 值显然就是 multiset 中最大的值。由于第一个 \(i\) 到开头和最后一个到两边也是一个未出现 \(i\) 的极长区间,那么初始向每个 set 中插入一个 \(0\) 和 \(n+1\) 就能很方便的解决这个问题。
那么怎么查询?容易想到线段树。我们直接维护每个段上最大值,查找直接在线段树上二分就做完了。
复杂度 \(\mathcal{O(n\log n)}\)。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define lx ll
inline lx qr()
{
char ch = getchar(); lx x = 0, f = 1;
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
return x * f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define ppp pair<pii, pii>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 5e5 + 5;
const int mod = 1e9 + 7;
int n, m;
int a[N];
set<int> L[N];
multiset<int> D[N];
set<int>::iterator it1, it2, it3;
multiset<int>::iterator it;
int v[N << 2];
namespace Wisadel
{
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define mid ((l + r) >> 1)
inline void Wpushup(int rt){v[rt] = max(v[ls], v[rs]);}
inline void Wbuild(int rt, int l, int r)
{
if(l == r)
{
it1 = it2 = L[l].begin();
it2++;
while(it2 != L[l].end())
{
D[l].insert(*it2 - *it1 - 1);
it1++, it2++;
}
v[rt] = *D[l].rbegin();
return ;
}
Wbuild(ls, l, mid), Wbuild(rs, mid + 1, r);
Wpushup(rt);
}
void Wupd(int rt, int l, int r, int x)
{
if(l == r)
{
v[rt] = *D[l].rbegin();
return ;
}
if(x <= mid) Wupd(ls, l, mid, x);
else Wupd(rs, mid + 1, r, x);
Wpushup(rt);
}
void Wmdf(int x)
{
it1 = L[a[x]].lower_bound(x);
it2 = it1; it2--;
it3 = it1; it3++;
it = D[a[x]].lower_bound(*it1 - *it2 - 1);
assert(it != D[a[x]].end());
D[a[x]].erase(it);
it = D[a[x]].lower_bound(*it3 - *it1 - 1);
assert(it != D[a[x]].end());
D[a[x]].erase(it);
D[a[x]].insert(*it1 - *it2), D[a[x]].insert(*it3 - *it1 - 2);
L[a[x]].erase(x);
L[a[x]].insert(x + 1);
it1 = L[a[x + 1]].lower_bound(x + 1);
it2 = it1; it2--;
it3 = it1; it3++;
it = D[a[x + 1]].lower_bound(*it1 - *it2 - 1);
D[a[x + 1]].erase(it);
it = D[a[x + 1]].lower_bound(*it3 - *it1 - 1);
D[a[x + 1]].erase(it);
D[a[x + 1]].insert(*it1 - *it2 - 2), D[a[x + 1]].insert(*it3 - *it1);
L[a[x + 1]].erase(x + 1);
L[a[x + 1]].insert(x);
Wupd(1, 1, n + 1, a[x]), Wupd(1, 1, n + 1, a[x + 1]);
swap(a[x], a[x + 1]);
}
int Wq(int rt, int l, int r, int x)
{
if(l == r) return l;
if(x <= v[ls]) return Wq(ls, l, mid, x);
else return Wq(rs, mid + 1, r, x);
}
short main()
{
freopen("star.in", "r", stdin), freopen("star.out", "w", stdout);
n = qr, m = qr;
fo(i, 1, n) a[i] = qr + 1, L[a[i]].insert(i), L[i].insert(0), L[i].insert(n + 1);
L[n + 1].insert(0), L[n + 1].insert(n + 1);
Wbuild(1, 1, n + 1);
fo(i, 1, m)
{
int op = qr, x = qr;
if(op == 1)
{
if(a[x] == a[x + 1]) continue;
else Wmdf(x);
}
else printf("%d\n", Wq(1, 1, n + 1, x) - 1);
}
return Ratio;
}
}
signed main(){return Wisadel::main();}
// Now there's only one thing I can do
// Fight until the end like I promised to
C. 勾指起誓
好像用什么 FMT,有点高级。
赛时由于不知道最后存活多个人也算赢挂了暴力的 12pts。
D. 第八交响曲
双调排序模板。
讨论发现定义是比较容易出现歧义的地方,比较官方一点的是:定义一个长度为 \(n\) 的序列为双调序列,当且仅当满足以下两个条件之一:
-
存在 \(k\in[1,n]\),满足 \(a_1\le a_2\le \cdots\le a_k\ge\cdots\ge a_{n-1}\ge a_n\) 或 \(a_1\ge a_2\ge\cdots\ge a_k\ge\cdots\ge a_{n-1}\ge a_n\)。简言之,是单峰序列。
-
循环移位后仍满足条件 1。
为方便讲述,先假定 \(n=2^m\)。Batcher 定理:将序列拆成等长的两半,对两个序列相对位置一样的数进行比较,大小分别放入不同序列的同一位置,最终得到的两个序列仍都是双调序列,且大序列里最小的数大于小序列里最大的数。
那么针对双调序列的双调排序就比较显然了,每次对当前区间做一次 Batcher 定理操作后,若长度大于 2,分别递归对左右区间做相同的操作。一共 \(\log n\) 层,每层会判断交换 \(\frac{n}{2}\) 次。
考虑扩展到对非双调序列的排序。改变排序策略是比较愚蠢的,因为对于非双调序列根本没有什么普遍规律可言。因此我们考虑将这个序列转变为双调序列,这个过程叫 Bitonic merge。将两个相邻的、单调性相反的单调序列看作一个双调序列, 每次将这两个相邻的,单调性相反的单调序列 merge生成一个新的双调序列, 然后排序。
放张图会好理解很多:
那么再考虑扩展到长度为非二的正整数次幂的序列,很简单,我们只需要将后面补全位置对应的数,然后排序就行了。
迁移到本道题,有不少细节要注意。由于是 SPJ 题,没办法真的去补上数做操作,我们只能通过改变双调后半个序列的单调性来使得后面的数一定满足当前所需。具体实现上就是如果当前需要一个单调递增的序列,那么需要使该序列成为左半个序列递减、右半个序列递增的双调序列。
由于本题可以将平行操作同时进行,所以次数是 \(\frac{\log n\times (\log n-1)}{2}\) 的。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define lx ll
inline lx qr()
{
char ch = getchar(); lx x = 0, f = 1;
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
return x * f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define ppp pair<pii, pii>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int n, ans;
vector<pii> when[N];
int mp[N];
namespace Wisadel
{
inline void Wsol(int l, int r, bool sheng, int tim)
{
ans = max(ans, tim);
int mid = (l + r) >> 1, len = r - l + 1;
int zmid = len / 2;
fo(i, l, mid)
{
if(i + zmid > n) break;
if(sheng) when[tim].P_B(M_P(i, i + zmid));
else when[tim].P_B(M_P(i + zmid, i));
}
if(r - l + 1 != 2) Wsol(l, mid, sheng, tim + 1), Wsol(mid + 1, r, sheng, tim + 1);
}
inline void Wturn(int l, int r, bool sheng, int tim)
{
int mid = (l + r) >> 1, len = r - l + 1;
if(r - l + 1 != 2) Wturn(l, mid, sheng ^ 1, tim - log2(len)), Wturn(mid + 1, r, sheng, tim - log2(len));
Wsol(l, r, sheng, tim - log2(len) + 1);
}
short main()
{
freopen("symphony.in", "r", stdin), freopen("symphony.out", "w", stdout);
n = qr;
int m = n;
if(n <= 8) m = 8;
else if(n <= 16) m = 16;
else if(n <= 32) m = 32;
else if(n <= 64) m = 64;
else m = 128;
int zc = 0, x = m / 2;
while(x % 2 == 0) zc += log2(x), x /= 2;
Wturn(1, m / 2, 0, zc), Wturn(m / 2 + 1, m, 1, zc);
Wsol(1, m, 1, zc + 1);
printf("%d\n", ans);
fo(i, 1, ans)
{
for(pii j : when[i])
printf("CMPSWP R%d R%d ", j.fi, j.se);
puts("");
}
return Ratio;
}
}
signed main(){return Wisadel::main();}
// Now there's only one thing I can do
// Fight until the end like I promised to
末
瞪了半场 T4,以为搞出 \(2^n\) 长度的序列就 AC 了,但是 SPJ 写的有一些牛,导致我以为是 SPJ 错了,被硬控 40min,等 huge 来了细研究才知道是思路错了。
T1 上来太急了,根本没沉下心去推性质,打了个搜过了所有样例就没管了,大失败。
中午思考了以下,改变了一些原有的观念。其实奥赛无论成败,都是高中生活中不可抹去的绚丽的一笔。有这种体验,其实就足够了。本来就零基础,撑不到最后很正常,天赋不在这呗,别人都只能卷文化课的时候,我已经尝试为之后的人生开了一条新路,即使最后奥赛只能作为爱好,也是其他人所没有的。心态放平,不要患得患失,尽人事,听天命,就好了。
又没忍住说了 p 话。