『模拟赛』多校A层冲刺NOIP2024模拟赛24(更新 T2 详解)
Rank
。
A. 选取字符串
签。
一眼想到动物园那个题面,kmp 求出的 next 数组实际上就是既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度。
那么可以想到,某个串除了它自身外,可选的最长的 p/q 即为它的 next。更短的可选,一定只能是 next 的 next。以此类推,知道 next 为 0,即前缀为空串。我们可以求得每个前缀作为 p/q 时可以被选的前缀数量,之后用组合数算一下即可。注意这个数量需要转移,以及边界情况的判断。时间复杂度 \(\mathcal{O(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 = 1e6 + 5;
const int mod = 998244353;
int n, k;
int kmp[N], cnt[N], tim[N];
ll jc[N], ny[N], ans;
string s;
namespace Wisadel
{
ll Wqp(ll x, int y)
{
ll res = 1;
while(y){if(y & 1) res = res * x % mod; x = x * x % mod; y >>= 1;}
return res;
}
void Wpre()
{
cnt[0] = 2;
int j = 0;
fo(i, 2, n)
{
while(j && s[j + 1] != s[i]) j = kmp[j];
if(s[j + 1] == s[i]) j++;
kmp[i] = j;
cnt[j]++;
}
}
int Wgettim(int x)
{
if(tim[x]) return tim[x];
if(!x) return tim[x] = 1;
return Wgettim(kmp[x]) + 1;
}
ll C(int n, int m)
{
if(n < m) return 0;
return jc[n] * ny[m] % mod * ny[n - m] % mod;
}
short main()
{
freopen("string.in", "r", stdin), freopen("string.out", "w", stdout);
k = qr; cin >> s; n = s.size(); s = " " + s;
jc[0] = ny[0] = 1;
fo(i, 1, n + 1) jc[i] = jc[i - 1] * i % mod;
ny[n + 1] = Wqp(jc[n + 1], mod - 2);
fu(i, n, 1) ny[i] = ny[i + 1] * (i + 1) % mod;
Wpre();
fo(i, 0, n) if(!tim[i]) tim[i] = Wgettim(i);
fu(i, n, 0)
{
ans = (ans + C(cnt[i] + (i != 0), k) * ((2 * tim[i] % mod - 1 + mod) % mod) % mod) % mod;
cnt[kmp[i]] += cnt[i];
}
printf("%lld\n", ans);
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. 取石子
一眼像原,但是并不会做。
赛时糊了 \(k\le 3\) 的部分分,然后 task1 多过了一个,所以 50pts。
正解学的 wang54321 的做法,有些神秘。考虑通过判断首次拿去某个位置的若干个石子后后手能否必胜来判断是否合法。通过根据当前的合法状态判断下一个可能的数量,可以达到 \(\mathcal{O(n\log V)}\) 的复杂度。
详细的明天写。
我们首先考虑决定完先手后手第一步要拿什么。发现只需要拿 2 的幂,即 1、2、4、8。为什么呢,因为其他的数与拿这些数是等价且劣于的。以 3 为例,3 相当于二人多拿了一轮 1,如果 1 必输拿 3 不可能赢,反而 1 必赢拿 3 不一定赢,因为对手多了一个选择。其他数同理,因此我们枚举这些一共 \(\log V\) 个数即可。
然后考虑如何快速判掉确定无解的数。以在某个位置上从 1 开始拿为例。对 1 的 check 很简单。如果拿 1 后手必败,那么拿 2、4、6、8 等全部偶数后手一定必胜,这是显然的,根据奇偶性得出。那么下一次我们就需要去 check 3。对 3 check 时,我们一定不会去让后手取 1 个,因为和 1 情况就一样了,一定输。所以我们会考虑从 2 开始拿,谁先拿完 2 谁赢,这个可以通过预处理可拿的 2 的数量快速判断。如果拿 3 后手依然赢不了,那么拿 5、9、13 等数一定能赢,因为这相当于多拿去一个 2,改变了拿 2 的结果,下次需 check 7;那么如果拿 3 后手必胜,那么拿 7、11 这些数后手一定都必胜,原因同理,下次需 check 5。而 check 5 或 7 时一定都只会考虑拿 4,因为拿其他的一定都赢不了。
综上,归纳发现,如果对某个数 check 成功了,那么下次需要 check 的数就是当前数加上该次后手取的数,否则下次 check 当前数加上二倍的该次后手取的数。
那么做法就很容易了,我们提前处理出整个序列只拿 1、2、4、8 这些数能拿多少个,对于序列中每个数考虑时先减去它的贡献,然后按上述操作进行 check,最后还原贡献即可。复杂度 \(\mathcal{O(n\log V)}\)。
点击查看代码
#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)
#define int ll
const int Ratio = 0;
const int N = 5e4 + 5;
const int mod = 998244353;
int n, k, tot, op;
ll a[N], sum[32];
vector<int> ans[N];
namespace Wisadel
{
bool Wck(ll id, ll now, ll Q)
{
ll zc = (a[id] - now) / (1 << (Q - 1)) + sum[Q];
return zc & 1;
}
short main()
{
// freopen(".in", "r", stdin), freopen(".out", "w", stdout);
freopen("nim.in", "r", stdin), freopen("nim.out", "w", stdout);
n = qr, k = qr;
fo(i, 1, n)
{
a[i] = qr;
fo(j, 1, 31) sum[j] += a[i] / (1 << (j - 1));
}
fo(i, 1, n)
{
fo(j, 1, 31) sum[j] -= a[i] / (1 << (j - 1));
ll now = 1, tim = 1;
while(now <= min(a[i], k))
{
if(Wck(i, now, tim)) now += (1 << (tim - 1));
else ans[i].P_B(now), now += (1 << tim), op = 1;
tim++;
}
fo(j, 1, 31) sum[j] += a[i] / (1 << (j - 1));
}
if(!op) puts("0");
else
{
puts("1");
fo(i, 1, n) for(auto j : ans[i]) printf("%d %d\n", i, j);
}
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
C. 均衡区间
考虑现将每个点作为极值的控制范围预处理出来,单调栈 \(\mathcal{O(n)}\) 完成。之后分别处理左右端点的答案,这样就转变成了一个二维数点的问题,其实就是扫描线,每个点的答案区间是好求的,树状数组维护一下就做完了,复杂度 \(\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 = 1e6 + 5;
const int mod = 998244353;
int n, id;
int a[N], L[N], R[N], le[N], ri[N], t[N];
stack<int> s1, s2;
pii zc[N];
namespace Wisadel
{
void Wpre()
{
fo(i ,1, n)
{
while(s1.size() && a[s1.top()] <= a[i]) s1.pop();
if(s1.empty()) L[i] = 1;
else L[i] = s1.top() + 1;
while(s2.size() && a[s2.top()] >= a[i]) s2.pop();
if(s2.empty()) L[i] = min(L[i], 1);
else L[i] = min(L[i], s2.top() + 1);
s1.push(i), s2.push(i);
}
while(s1.size()) s1.pop();
while(s2.size()) s2.pop();
fu(i, n, 1)
{
while(s1.size() && a[s1.top()] <= a[i]) s1.pop();
if(s1.empty()) R[i] = n;
else R[i] = s1.top() - 1;
while(s2.size() && a[s2.top()] >= a[i]) s2.pop();
if(s2.empty()) R[i] = max(R[i], n);
else R[i] = max(R[i], s2.top() - 1);
s1.push(i), s2.push(i);
}
}
inline void Wadd(int x){for(; x <= n; x += (x & -x)) t[x]++;}
inline int Wq(int x){int res = 0; for(; x; x -= (x & -x)) res += t[x]; return res;}
short main()
{
freopen("interval.in", "r", stdin), freopen("interval.out", "w", stdout);
n = qr, id = qr;
fo(i, 1, n) a[i] = qr, L[i] = n;
Wpre();
fo(i, 1, n) zc[i] = M_P(L[i] - 1, i);
sort(zc + 1, zc + 1 + n, [](pii A, pii B){return A.fi < B.fi;});
int j = 1;
fo(i, 1, n)
{
while(j <= zc[i].fi) Wadd(R[j++]);
ri[zc[i].se] = Wq(zc[i].se - 1);
}
fo(i, 1, n) t[i] = 0, zc[i] = M_P(R[i] + 1, i);
sort(zc + 1, zc + 1 + n, [](pii A, pii B){return A.fi > B.fi;});
j = n;
fo(i, 1, n)
{
while(j >= zc[i].fi) Wadd(L[j--]);
le[zc[i].se] = Wq(n) - Wq(zc[i].se);
}
fo(i, 1, n) printf("%d ", le[i]); puts("");
fo(i, 1, n) printf("%d ", ri[i]); puts("");
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
D. 禁止套娃
题还没看(逃
dp,明天一起改。
末
打的有点唐说实话,20min 写完 T1 代码结果忘改模数被硬控了将近 1h,然后死磕 T2,T3 正解最后有思路了没打完,遗憾离场。
最近越来越忙了,题解可能也不如之前详细了,也就大概说说做法和关键细节啥的。
发第二轮分数线了,一等线还挺低,√7 和 √6 分别卡掉一车人,还好我的得分比较玄学,根本卡不到我。
学了一年√6,只能说天赋几乎是没有的,有的全是这一年时间积累的经验吧。想开以后其实也是个挺好的结果了,至少比那些尝试的机会都没有的人强不是吗?
完结撒花~
还是图图