闲话 11.25
- 上午
vp NOIP 2020。
太菜了,就会前三道(。
排水系统
签。
第一眼还以为是网络流,后来仔细一看发现就是个小水题。
由于保证了不存在环和重边,流向是一定的且与时间无关,那么容易想到对每个点分别考虑,做一遍拓扑排序就结束了。
关键点在于如何输出分数。赛场上实现一个分数类是不现实的,因此考虑用精度高一些的类型焯过去。__int128
就是一个很好的选择,每次加水时注意一些 \(\gcd\) 和 \(\operatorname{lcm}\) 的实现细节就没了。
点击查看代码
#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 long double ld;
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 pll pair<ll, ll>
#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 = 1e8;
int n, m;
int rd[N], cd[N];
int hh[N], to[N], ne[N], cnt;
lll fz[N], fm[N];
namespace Wisadel
{
inline void Wadd(int u, int v)
{
to[++cnt] = v;
ne[cnt] = hh[u];
hh[u] = cnt;
}
inline void Wtopsort()
{
queue<int> q;
fo(i, 1, n) if(!rd[i]) q.push(i), fz[i] = fm[i] = 1;
else fm[i] = 1;
while(q.size())
{
int u = q.front(); q.pop();
if(!cd[u]) continue;
lll zfz = 0, zfm = 0;
if(fz[u] % cd[u] == 0) zfz = fz[u] / cd[u], zfm = fm[u];
else zfz = fz[u], zfm = fm[u] * cd[u];
for(int i = hh[u]; i != -1; i = ne[i])
{
int v = to[i];
lll zc = __gcd(zfm, fm[v]);
zc = zfm * fm[v] / zc;
fz[v] *= zc / fm[v], fm[v] = zc;
lll zcc = zfz * zc / zfm;
fz[v] += zcc;
zc = __gcd(fz[v], fm[v]);
fz[v] /= zc, fm[v] /= zc;
rd[v]--;
if(!rd[v]) q.push(v);
}
}
}
int st[N], top;
inline void Wprint(lll x)
{
if(x == 0) putchar('0');
else
{
while(x) st[++top] = x % 10, x /= 10;
while(top) putchar(st[top--] + '0');
}
}
short main()
{
n = qr, m = qr;
memset(hh, -1, sizeof hh);
fo(i, 1, n)
{
cd[i] = qr;
fo(j, 1, cd[i])
{
int x = qr;
Wadd(i, x);
rd[x]++;
}
}
Wtopsort();
fo(i, 1, n) if(!cd[i])
Wprint(fz[i]), putchar(' '), Wprint(fm[i]), puts("");
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
字符串匹配
简单计算复杂度发现暴力可行,直接上。
考虑枚举每一个 \(AB\),然后调和级数找所有满足 \((AB)^i\) 的串,哈希轻松做。那么问题就来到了奇数个数的限制。
考虑直接维护关于奇数字符个数的前缀和与后缀和。考虑到每一个 \(AB\) 中 \(A\) 的取值区间为 \([1,|AB|-1]\),我们直接枚举右端点,那么到某一位时我们可以取到其前面所有的贡献,一路加过去即可。然后对于一个合法的 \((AB)^i\),直接计算 \(C\) 中奇数次字符的个数对应的贡献即可。时间复杂度略高 \(\mathcal{O(Tn(\log n+26))}\)。
点击查看代码
#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 fi first
#define se second
#define pii pair<int, int>
#define P_B(x) push_back(x)
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = (1 << 20) + 5;
const int bas = 233;
int n;
string s;
int b[N], zj[N], hj[N], cnt[N];
ull has[N], p[N];
namespace Wisadel
{
inline ull Wgh(int l, int r){return has[r] - has[l - 1] * p[r - l + 1];}
short main()
{
p[0] = 1;
fo(i, 1, (1 << 20)) p[i] = p[i - 1] * bas;
int T = qr;
while(T--)
{
cin >> s; n = s.size(); s = " " + s;
fo(i, 1, n) has[i] = has[i - 1] * bas + s[i];
int sum = 0;
fo(i, 0, 26) b[i] = cnt[i] = 0;
fo(i, 1, n)
{
int zc = s[i] - 'a';
b[zc] ^= 1;
sum += b[zc] ? 1 : -1;
zj[i] = sum;
}
sum = 0;
fill(b, b + 27, 0);
fu(i, n, 1)
{
int zc = s[i] - 'a';
b[zc] ^= 1;
sum += b[zc] ? 1 : -1;
hj[i] = sum;
}
ll ans = 0;
fo(i, 2, n)
{
fo(j, zj[i - 1], 26) cnt[j]++;
for(int j = i; j < n && has[i] == Wgh(j - i + 1, j); j += i) ans += cnt[hj[j + 1]];
}
printf("%lld\n", ans);
}
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
C. 移球游戏
比较有思维含量的一道题。
难得 CCF 思路引导得很好,那就跟着思路过一遍。
首先发现大样例 2 给了一个 \(n=2\) 的情况,跟随手模可以发现正解的大致实现过程:
-
将柱子二上分出 \(s\) 个到柱子三,\(s\) 为柱子一中 \(1\) 的个数。
-
将柱子一上 \(1\) 放到柱子二,剩下放到柱子三。此时柱子一为空,柱子二三是满的。
-
将柱子二上 \(s\) 个 \(1\) 放回柱子一,柱子三上 \(m-s\) 个 \(2\) 放回柱子二。此时柱子一是满的。
-
将柱子二上全部放到柱子三,将柱子一上 \(m-s\) 个 \(2\) 放到柱子二。此时柱子一上 \(s\) 个 \(1\),柱子二上 \(m-s\) 个 \(2\)。
-
将柱子三上 \(1\) 放到柱子一,\(2\) 放到柱子二。排序完成。
这样操作次数是在 \([4m,5m]\) 之内的,可以接受。不过这只适用于 \(n=2\) 的情况,如何扩展呢?
如果我们通过操作使两种球全部出现在两根柱子上,显然就可以进行上面的操作。如何使两种球出现在两根柱子上?如果我们通过操作使四种球出现在四根柱子上,我们就可以进行至多 \(2\times 2=4\) 次类似的操作就可以实现。那么以此类推,可以发现每次操作相当于处理好后二分成左右两组再次操作。整体上是一种分治的思想。
那么考虑扩展后这个“类似”的操作如何实现。我们只需要将颜色编号为前一半的都放在前一半的柱子上,所以颜色不超过 \(mid\) 与否就对应着 \(n=2\) 中的 \(1/2\)。那么还有一个问题,如果我选的两根柱子上不超过和超过 \(mid\) 的颜色的球数量并不相等怎么办?很简单,一定有一种颜色是超过半数的,我们的操作是一定可以实现一种颜色放置完全的,简单分讨处理多的那一种即可。
分析复杂度,操作总次数递归式为 \(T(n)=2\times T(\frac{n}{2})+ n = n\log n\),每次操作按 \(5m\) 计算,总操作次数为 \(5nm\log n\),时间复杂度为 \(\mathcal{O(n^2m)}\)。
点击查看代码
#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;
#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 fi first
#define se second
#define pii pair<int, int>
#define P_B(x) push_back(x)
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 50 + 5, M = 400 + 5;
int n, m;
int st[N][M], top[N], ok[N];
vector<pii> ans;
namespace Wisadel
{
inline void Wmov(int x, int y)
{
ans.P_B(M_P(x, y));
st[y][++top[y]] = st[x][top[x]--];
}
inline void Wsol(int l, int r)
{
if(l == r) return ;
int mid = (l + r) >> 1;
fo(i, l, r) ok[i] = 0;
fo(i, 1, mid) fo(j, mid + 1, r)
{
if(ok[i] || ok[j]) continue;
int zci = 0, zcj = 0;
fo(k, 1, m) zci += (st[i][k] <= mid), zcj += (st[j][k] <= mid);
if(zci + zcj >= m)
{
fo(gg, 1, zci) Wmov(j, n + 1);
while(top[i]) Wmov(i, st[i][top[i]] <= mid ? j : n + 1);
fo(gg, 1, zci) Wmov(j, i);
fo(gg, 1, m - zci) Wmov(n + 1, i);
while(top[j]) Wmov(j, n + 1);
fo(gg, 1, m - zci) Wmov(i, j);
while(top[n + 1]) Wmov(n + 1, (st[n + 1][top[n + 1]] <= mid && top[i] < m) ? i : j);
ok[i] = 1;
}
else
{
zci = m - zci, zcj = m - zcj;
fo(gg, 1, zcj) Wmov(i, n + 1);
while(top[j]) Wmov(j, st[j][top[j]] > mid ? i : n + 1);
fo(gg, 1, zcj) Wmov(i, j);
fo(gg, 1, m - zcj) Wmov(n + 1, j);
while(top[i]) Wmov(i, n + 1);
fo(gg, 1, m - zcj) Wmov(j, i);
while(top[n + 1]) Wmov(n + 1, (st[n + 1][top[n + 1]] > mid && top[j] < m) ? j : i);
ok[j] = 1;
}
}
Wsol(l, mid), Wsol(mid + 1, r);
}
short main()
{
n = qr, m = qr;
fo(i, 1, n)
{
top[i] = m;
fo(j, 1, m) st[i][j] = qr;
}
Wsol(1, n);
printf("%d\n", (int)ans.size());
for(pii i : ans) printf("%d %d\n", i.fi, i.se);
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
- 下午
改上午打的 NOIP 2020。
帮助 jijidawang 贺题验题,学到了很多可能很基础但我并不知道的推式子技巧。
还剩四天,但其实完整的就三天了。
想到三天后自己可能就永远离开这熟悉的地方就有些失落,但想得更多的还是正常或超常发挥之类的。光想不做肯定不行,还是得落实到刷的每一道题。三天,对于查漏补缺肯定绰绰有余,认真,平和地对待,迎接属于自己的最好的结果。