『模拟赛』多校A层冲刺NOIP2024模拟赛04
Rank
赤石场。
A. 02表示法
签。
若干天前在洛谷随到过,不过当时只看了眼讨论区就走了www 还好本来不是很难。
发现大体上是一个拆分二的幂的问题,从大到小枚举 2 的幂,判断有没有这个幂只用比较大小关系,然后再对指数做一个同样的操作,递归至不大于 2 为止,注意 \(2^1\) 不用输出 (1)
。
发现数据范围达到了惊人的 \(10^{180}\),不过它约等于 \(2^{598}\),也就是说对这做法没有影响,只是二的幂要用高精的形式体现。只用到了高精比较,高精乘低精和高精减三个操作,不是很难写。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#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 fi first
#define se second
const int Ratio = 0;
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int pf[N], tot;
int s[600][200], a[200], zc[200];
string op, ss;
namespace Wisadel
{
bool Wcmp(int a[], int b[])
{
if(a[0] != b[0]) return a[0] > b[0];
fu(i, a[0], 1) if(a[i] != b[i]) return a[i] > b[i];
return 1;
}
void Wp(int id, int b)
{
fo(i, 0, s[id][0]) a[i] = s[id][i];
memset(zc, 0, sizeof zc);
int x = 0, len = a[0];
fo(i, 1, len)
{
zc[i] = a[i] * b + x;
x = zc[i] / 10;
zc[i] %= 10;
}
if(x) len++, zc[len] = x;
zc[0] = len;
fo(i, 0, len) s[id + 1][i] = zc[i];
}
void Wjian(int id)
{
fo(i, 1, s[id][0])
{
if(a[i] >= s[id][i]) a[i] -= s[id][i];
else a[i + 1]--, a[i] = a[i] + 10 - s[id][i];
}
while(!a[a[0]]) a[0]--;
}
void Wpre()
{
s[0][0] = s[0][1] = 1;
fo(i, 1, 598) Wp(i - 1, 2);
}
string Wdfsc(int x)
{
if(x == 0) return "0";
string sss = "";
bool tou = 1;
fu(i, 12, 0)
{
if(x >= pf[i])
{
if(tou) tou = 0;
else sss += "+";
if(i == 1) sss += "2";
else sss += "2(" + Wdfsc(i) + ")";
x -= pf[i];
}
}
return sss;
}
void Wdfs()
{
if(a[0] == 1 && a[1] == 1){ss += "2(0)"; return ;}
if(a[0] == 1 && a[1] == 2){ss += "2"; return ;}
bool tou = 1;
fu(i, 598, 0)
{
if(Wcmp(a, s[i]))
{
if(tou) tou = 0;
else ss += "+";
if(i == 1) ss += "2";
else ss += "2(" + Wdfsc(i) + ")";
Wjian(i);
}
}
}
short main()
{
freopen("pow.in", "r", stdin) , freopen("pow.out", "w", stdout);
Wpre();
cin >> op;
a[0] = op.size();
fo(i, 1, a[0]) a[i] = (int)op[i - 1] - '0';
fo(i, 1, a[0]) zc[i] = a[a[0] - i + 1];
fo(i, 1, a[0]) a[i] = zc[i];
pf[0] = 1;
fo(i, 1, 12) pf[i] = pf[i - 1] * 2;
Wdfs();
cout << ss << endl;
return Ratio;
}
}
int main(){return Wisadel::main();}
B. 子串的子串
开场听到 5k 的赞叹后思维就被莫队固定住了,想不到什么很优的 add 操作,最后复杂度大概是 \(\mathcal{O(n^2q)}\) 的(?),拿到 70pts。
考虑正解。发现不带修,因此答案不变,我们可以提前处理出来。设 \(ans_{l,r}\) 表示 \(\left[l,r\right]\) 内本质不同子串个数。枚举串长和左端点,用求出每个串的哈希值,开一个 map 记录该串上次出现的左端点 \(las\),此时 \(\left[las,r\right]\) 显然出现了一个重复串,使 \(ans_{las,r}\) 减一。最后从一端开始枚举所有串,根据二维前缀和的求法可知:
当然还有个更好理解的思路,如图:
相当于是 \(\left[l,r\right]\) 原本的限制,加上串 \(s_{l,r}\) 本身,然后加 \(ans_{l+1,r}\) 与 \(ans_{l,e-1}\) 时将 \(ans_{l+1,r-1}\) 多算了一遍,减去即可。然后就可以 \(\mathcal{O(1)}\) 查询了,总复杂度是 \(\mathcal{O(n^2+q)}\) 的。
int_R 打了 SAM 效率薄纱所有人%%%。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 fi first
#define se second
const int Ratio = 0;
const int N = 3000 + 5;
const int base = 233;
int n, m;
string s;
ull has[N], k[N];
unordered_map<ull, int> mp;
int ans[N][N];
namespace Wisadel
{
ull Whash(int l, int r){return has[r] - has[l - 1] * k[r - l + 1];}
short main()
{
freopen("substring.in", "r", stdin) , freopen("substring.out", "w", stdout);
n = qr, m = qr;
cin >> s; s = " " + s; k[0] = 1;
fo(i, 1, n) has[i] = has[i - 1] * base + s[i], k[i] = k[i - 1] * base;
fo(len, 1, n)
{
mp.clear();
fo(l, 1, n - len + 1)
{
int r = l + len - 1;
ull zc = Whash(l, r);
ans[mp[zc]][r]--;
mp[zc] = l;
}
}
fu(l, n, 1) fo(r, l, n)
ans[l][r] += ans[l + 1][r] + ans[l][r - 1] -ans[l + 1][r - 1] + 1;
fo(i, 1, m) printf("%d\n", ans[qr][qr]);
return Ratio;
}
}
int main(){return Wisadel::main();}
C. 魔法咒语
赛时想到了正反建两棵 trie 树但不知道怎么容斥计数于是止步 20pts。
忘了之前的套路,容斥不会就分讨,不重就不用容斥。我们建反向 trie 树时记录每一种字母节点的数量,然后遍历正向 trie 树的节点,考虑怎么计数。遍历其 26 种子节点,如果该节点没有某种字母的子节点,则此时拼接该字母开头的后缀时一定没有重复情况,那么令 \(ans+=cnt_{该字母}\) 即可。
发现当前缀串取到一个完整的串时,一定可以在其后补一个与最后一个字母相同的字母使其为新串,所以记录一下每个字母是否是某一个串的结尾,然后在上述判断为否的时候再进行判断即可。
但是这种计数局限于串可拆分的情况,对于一个单字母组成的串发现它不会被算入,而两个这样的串又显然能构成一个新串,因此输入时对于这种串直接特判即可。复杂度大概是 \(\mathcal{O(正向\ trie\ 树上节点数\times 26)}\) 的。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#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 fi first
#define se second
const int Ratio = 0;
const int N = 4e5 + 5;
const int mod = 1e9 + 7;
int n, tot, oto;
string s[N];
int to[N][26], v[26], ot[N][26], yz[26], fla[26];
ll ans;
namespace Wisadel
{
void Wins(string s)
{
int now = 0, len = s.size();
fo(i, 0, len - 1)
{
int zc = s[i] - 'a';
if(!to[now][zc]) to[now][zc] = ++tot;
now = to[now][zc];
}
}
void Wsni(string s)
{
int now = 0, len = s.size();
fu(i, len - 1, 0)
{
int zc = s[i] - 'a';
if(!ot[now][zc]) ot[now][zc] = ++oto, v[zc]++;
now = ot[now][zc];
}
}
short main()
{
freopen("magic.in", "r", stdin) , freopen("magic.out", "w", stdout);
n = qr;
fo(i, 1, n)
{
cin >> s[i];
Wins(s[i]), Wsni(s[i]);
yz[s[i][s[i].size() - 1] - 'a'] = 1;
if(s[i].size() == 1 && !fla[s[i][0] - 'a']) ans++, fla[s[i][0] - 'a'] = 1;
}
fo(i, 1, tot) fo(j, 0, 25)
if(!to[i][j]) ans += v[j];
else if(yz[j]) ans++;
printf("%lld\n", ans);
return Ratio;
}
}
int main(){return Wisadel::main();}
D. 表达式
挺妙的一道题,赛时由于没开 long long
导致没拿到 15pts 暴力分。
这题用到了数据点分治,本来还以为没有题正解会是这玩意。首先对于 Subtask1,直接 \(\mathcal{O(nm)}\) 暴力即可,主要考虑其它的子任务。
发现其它的子任务的共性是模数拆成不互质的形式的个数后非常少也非常小,拆后最大的模数也只有 29,我们完全可以将其拆开分别计算对于不同模数的不同答案。而且加、乘和乘方都是支持取模运算的,那么就有 \(f(x)=f(x\mod p)\)。
我们可以直接暴力处理出 \(\left[0,p-1\right]\) 的所有函数值,用线段树维护,主要操作就是一个线段树合并的过程,而这个又非常简单,只用把左子树的函数值代到右子树的函数里求值即可。这样我们就得到了所有对拆开后的模数取模的答案,写出来就是这个:
发现就是 crt 的基本形式,然后直接跑就完了。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#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 pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 2e5 + 5;
namespace Wistask1
{
int n, m, mod;
pii d[N];
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 Wwork(ll x)
{
fo(i, 1, n)
{
if(d[i].fi == 1) x = (x + d[i].se) % mod;
else if(d[i].fi == 2) x = x * d[i].se % mod;
else x = Wqp(x, d[i].se);
}
printf("%lld\n", x);
}
short main()
{
n = qr, m = qr, mod = qr;
fo(i, 1, n)
{
string s; cin >> s;
if(s[0] == '+') d[i].fi = 1;
else if(s[0] == '*') d[i].fi = 2;
else d[i].fi = 3;
ll num = 0;
fo(j, 1, s.size() - 1) num = num * 10 + s[j] - '0';
d[i].se = num;
}
fo(i, 1, m)
{
int op = qr, x = qr;
if(op == 1) Wwork(x);
else
{
string s; cin >> s;
if(s[0] == '+') d[x].fi = 1;
else if(s[0] == '*') d[x].fi = 2;
else d[x].fi = 3;
ll num = 0;
fo(j, 1, s.size() - 1) num = num * 10 + s[j] - '0';
d[x].se = num;
}
}
return Ratio;
}
}
namespace Wvictoria
{
int n, m, M;
int yz[100], tot, pri[26], mod[26], gs;
pil d[N];
struct rmm
{
int mod;
ll Wqp(ll x, ll y)
{
ll res = 1;
while(y){if(y & 1) res = res * x % mod; x = x * x % mod; y >>= 1;}
return res;
}
int f[N << 2][30];
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define mid ((l + r) >> 1)
void Wpushup(int rt)
{
fo(i, 0, mod - 1) f[rt][i] = f[rs][f[ls][i]];
}
void Wbuild(int rt, int l, int r)
{
if(l == r)
{
fo(i, 0, mod - 1)
{
if(d[l].fi == 1) f[rt][i] = (i + d[l].se) % mod;
else if(d[l].fi == 2) f[rt][i] = d[l].se * i % mod;
else f[rt][i] = Wqp(i, d[l].se);
}
return ;
}
Wbuild(ls, l, mid), Wbuild(rs, mid + 1, r);
Wpushup(rt);
}
void Wupd(int rt, int l, int r, int x, int op, ll v)
{
if(l == r)
{
fo(i, 0, mod - 1)
{
if(op == 1) f[rt][i] = (i + v) % mod;
else if(op == 2) f[rt][i] = v * i % mod;
else f[rt][i] = Wqp(i, v);
}
return ;
}
if(x <= mid) Wupd(ls, l, mid, x, op, v);
else Wupd(rs, mid + 1, r, x, op, v);
Wpushup(rt);
}
} t[5];
void Wexgcd(int a, int b, int &x, int &y)
{
if(b == 0){x = 1, y = 0; return ;}
Wexgcd(b, a % b, x, y);
int z = x;
x = y, y = z - (a / b) * y;
}
short main()
{
n = qr, m = qr, M = qr;
fo(i, 2, 99)
{
if(!yz[i]) pri[++tot] = i;
fo(j, 1, tot)
{
if(pri[j] * i > 99) break;
yz[pri[j] * i] = 1;
if(i % pri[j] == 0) break;
}
}
int zc = M;
fo(i, 1, tot)
{
bool yw = 0;
while(zc % pri[i] == 0)
{
if(!yw) yw = 1, mod[++gs] = pri[i];
else mod[gs] *= pri[i];
zc /= pri[i];
}
}
fo(i, 1, n)
{
string s; cin >> s;
if(s[0] == '+') d[i].fi = 1;
else if(s[0] == '*') d[i].fi = 2;
else d[i].fi = 3;
ll num = 0;
fo(i, 1, s.size() - 1) num = num * 10 + s[i] - '0';
d[i].se = num;
}
fo(i, 1, gs)
{
t[i].mod = mod[i];
t[i].Wbuild(1, 1, n);
}
fo(i, 1, m)
{
int op = qr, x = qr;
if(op == 1)
{
ll ans = 0;
fo(j, 1, gs)
{
int w = t[j].f[1][x % mod[j]];
int md1 = M / mod[j], inv, y;
Wexgcd(md1, mod[j], inv, y);
ans = (ans + 1ll * w * md1 % M * (inv + mod[j]) % M) % M;
}
printf("%lld\n", ans);
}
else
{
string s; cin >> s;
ll zc = 0, num = 0;
if(s[0] == '+') zc = 1;
else if(s[0] == '*') zc = 2;
else zc = 3;
fo(j, 1, s.size() - 1) num = num * 10 + s[j] - '0';
fo(j, 1, gs) t[j].Wupd(1, 1, n, x, zc, num);
}
}
return Ratio;
}
}
namespace Wisadel
{
short main()
{
freopen("expr.in", "r", stdin) , freopen("expr.out", "w", stdout);
int u = qr;
if(u <= 3) return Wistask1::main();
return Wvictoria::main();
return Ratio;
}
}
int main(){return Wisadel::main();}
末
别的先不说了,补眼睛鼻托去了。。。 field 效率真高。
打的一般其实,T1 一上来没直接算高精可不可行而是直接打表找规律去了,然后啥也没找到,1.5h 的时候才打完。打 T2 一上来想莫队,然后发现并不能 \(\mathcal{O(1)}\) 转移。T3 最后 1h 想出来建两棵 trie,但是怎么也想不到计数方法,T4 更是直接挂挂挂。
一眼三道字符串题,不知道的还以为在打 abc。。
明天准备复习学习一下 crt 相关的,先立个 flag。