CrCPC 2024 做题笔记 / 代码集
CrCPC 2024 做题笔记 / 代码集
[CrCPC 2024] A - AI 的末日
随便维护一下转移即可。
瓶颈离散化,复杂度 \(O(n \log n)\)。
ケロシの代码
const int N = 1e6 + 5;
int n, k, a[N];
int b[N], len;
int f[N], mn, cnt;
void solve() {
cin >> n >> k;
FOR(i, 1, n) cin >> a[i];
FOR(i, 1, n) b[++ len] = a[i];
sort(b + 1, b + len + 1);
len = unique(b + 1, b + len + 1) - b - 1;
FOR(i, 1, n) a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
if(len < k) {
cout << 0 << endl;
return;
}
cnt = k;
FOR(i, 1, n) {
if(f[a[i]] == mn) {
cnt --;
if(! cnt) mn ++, cnt = k - 1, f[a[i]] = mn + 1;
else f[a[i]] = mn + 1;
}
}
cout << mn << endl;
}
[CrCPC 2024] B - 平凡的数论题
考虑因为 \(B > \max a_i,b_i,c_i\),所以 \(B\) 可以当成进制考虑,右边的式子永远不会进位,左边的式子会随着 \(B\) 增大而进位减少,所以这个式子是关于 \(B\) 单调的,直接二分即可。
时间复杂度 \(O(n^2 \log V)\)。
ケロシの代码
const int N = 2e3 + 15;
const ll LNF = 2e18;
int n, m, k, mx;
int a[N], b[N], c[N];
ll f[N];
void solve() {
cin >> n; ROF(i, n - 1, 0) cin >> a[i];
cin >> m; ROF(i, m - 1, 0) cin >> b[i];
cin >> k; ROF(i, k - 1, 0) cin >> c[i];
int mx = 0, len = max(n + m + 1, k);
REP(i, n) chmax(mx, a[i]);
REP(i, m) chmax(mx, b[i]);
REP(i, k) chmax(mx, c[i]);
ll L = mx + 1, R = LNF;
while(L <= R) {
ll mid = L + R >> 1;
FOR(i, 0, len) f[i] = 0;
REP(i, n) REP(j, m) {
f[i + j] += 1ll * a[i] * b[j];
f[i + j + 1] += f[i + j] / mid;
f[i + j] %= mid;
}
bool ok = 1;
ROF(i, len, 0) {
if(f[i] > c[i]) { L = mid + 1; ok = 0; break; }
if(f[i] < c[i]) { R = mid - 1; ok = 0; break; }
}
if(ok) {
cout << mid << endl;
return;
}
}
cout << "impossible" << endl;
}
[CrCPC 2024] C - 修路
时间复杂度 \(O(n^2 \log n)\)。
ケロシの代码
const int N = 1.5e3 + 5;
const ll LNF = 1e18;
struct Point {
ll x, y;
Point(ll x = 0, ll y = 0): x(x), y(y) {}
};
struct Vector {
ll x, y;
Vector(ll x = 0, ll y = 0): x(x), y(y) {}
};
Vector operator - (Point A, Point B) {
return Vector(A.x - B.x, A.y - B.y);
}
ll Dot(Vector A, Vector B) {
return A.x * B.x + A.y * B.y;
}
ll Cross(Vector A, Vector B) {
return A.x * B.y - A.y * B.x;
}
double Length(Vector A) {
return sqrt(Dot(A, A));
}
double Angle(Vector A, Vector B) {
return acos(Dot(A, B) / Length(A) / Length(B));
}
int n; double K;
Point a[N];
double F[N][2], A[N], B[N];
int p[N], len;
struct fenwick {
int c[N];
void clear() {
FOR(i, 1, n) c[i] = 0;
}
int lowbit(int x) {
return - x & x;
}
void modify(int u, int x) {
for(int i = u; i <= n; i += lowbit(i))
c[i] += x;
}
void modify(int l, int r, int x) {
modify(l, x);
modify(r + 1, - x);
}
int query(int u) {
int res = 0;
for(int i = u; i; i -= lowbit(i))
res += c[i];
return res;
}
} t;
void solve() {
cin >> n >> K;
FOR(i, 1, n) cin >> a[i].x >> a[i].y;
FOR(i, 2, n) F[i][0] = F[i][1] = LNF;
FOR(i, 1, n - 1) {
chmin(F[i][0], F[i][1] + K);
chmin(F[i][1], F[i][0] + K);
chmin(F[i + 1][0], F[i][0] + Length(a[i + 1] - a[i]));
chmin(F[i + 1][1], F[i][1] + Length(a[i + 1] - a[i]));
FOR(j, i + 1, n) A[j] = Angle(Vector(- 1, 0), a[j] - a[i]);
len = 0;
FOR(j, i + 1, n) B[++ len] = A[j];
sort(B + 1, B + len + 1);
FOR(j, i + 1, n) p[j] = lower_bound(B + 1, B + len + 1, A[j]) - B;
t.clear();
FOR(j, i + 2, n) {
int u = p[j] > p[i + 1];
int v = Cross(a[i] - a[j], a[j - 1] - a[j]) < 0;
chmin(F[j][v], F[i][u] + Length(a[j] - a[i]) + K * t.query(p[j]));
int l = p[j - 1], r = p[j];
if(l > r) swap(l, r);
t.modify(l + 1, r - 1, 1);
}
}
cout << fixed << setprecision(10);
cout << min(F[n][0], F[n][1]) << endl;
}
[CrCPC 2024] D - 排序
考虑每次变换有 \(\binom{n+3}{3}\) 种情况,直接对于所有的排列做 bfs 复杂度会爆炸。
注意到答案小于等于 \(6\),所以可以双向 bfs,每次最多搜三步。
那么时间复杂度 \(O(\binom{n+3}{3}^3n\log n)\)。
ケロシの代码
const int N = 12;
const int M = 4e6 + 5;
const int INF = 1e9 + 7;
int n, fac[N];
int F[M], G[M];
struct fenwick {
int c[N];
void clear() {
FOR(i, 1, n) c[i] = 0;
}
inline int lowbit(int x) {
return - x & x;
}
void modify(int u) {
for(int i = u + 1; i <= n; i += lowbit(i))
c[i] ++;
}
int query(int u) {
int res = 0;
for(int i = u + 1; i; i -= lowbit(i))
res += c[i];
return res;
}
} t;
int inc(vector<int> a) {
t.clear();
int res = 0;
REP(i, n) {
res += (a[i] - t.query(a[i] - 1)) * fac[n - i - 1];
t.modify(a[i]);
}
return res;
}
vector<int> dec(int res) {
vector<bool> f(n);
vector<int> a(n);
REP(i, n) {
int x = res / fac[n - i - 1];
res %= fac[n - i - 1];
int c = 0;
REP(j, n) if(! f[j]) {
if(c == x) {
a[i] = j;
f[j] = 1;
break;
}
c ++;
}
}
return a;
}
void solve() {
cin >> n;
vector<int> a(n);
REP(i, n) cin >> a[i], a[i] --;
fac[0] = 1;
FOR(i, 1, n) fac[i] = fac[i - 1] * i;
REP(i, fac[n]) F[i] = G[i] = INF;
queue<int> q;
F[inc(a)] = 0; q.push(inc(a));
while(! q.empty()) {
int u = q.front();
q.pop();
vector<int> a = dec(u);
FOR(i, - 1, n - 1) FOR(j, i, n - 1) FOR(k, j, n - 1) {
vector<int> b;
FOR(p, j + 1, k) b.push_back(a[p]);
FOR(p, 0, i) b.push_back(a[p]);
FOR(p, k + 1, n - 1) b.push_back(a[p]);
FOR(p, i + 1, j) b.push_back(a[p]);
int v = inc(b);
if(F[v] == INF) {
F[v] = F[u] + 1;
if(F[v] <= 2) q.push(v);
}
}
}
REP(i, n) a[i] = i;
G[inc(a)] = 0; q.push(inc(a));
while(! q.empty()) {
int u = q.front();
q.pop();
vector<int> a = dec(u);
FOR(i, - 1, n - 1) FOR(j, i, n - 1) FOR(k, j, n - 1) {
vector<int> b;
FOR(p, i + 1, j) b.push_back(a[p]);
FOR(p, k + 1, n - 1) b.push_back(a[p]);
FOR(p, 0, i) b.push_back(a[p]);
FOR(p, j + 1, k) b.push_back(a[p]);
int v = inc(b);
if(G[v] == INF) {
G[v] = G[u] + 1;
if(G[v] <= 2) q.push(v);
}
}
}
int ans = INF;
REP(i, fac[n]) chmin(ans, F[i] + G[i]);
cout << ans << endl;
}
[CrCPC 2024] E - 萌萌交互题
由于要猜的序列随机,所以直接从 \(1\) 开始一个一个试即可,那么代价就是序列的最大值。
枚举每一个最大值有多少种方案,不难想到转化成最大值小于等于一个数的方案,然后做差分。
那么推公式后就变成了一个 \(k\) 次幂和问题。
忽略快速幂和逆元,时间复杂度 \(O(n)\)。
ケロシの代码
const int N = 1e6 + 5;
const int P = 1e9 + 7;
typedef Modint<P> mint;
mint fac[N], f[N][2], s[N];
int fp(int x, int y) {
int res = 1;
for(; y; y >>= 1) {
if(y & 1) res = (1ll * res * x) % P;
x = (1ll * x * x) % P;
}
return res;
}
mint calc(mint n, int k) {
fac[0] = f[0][0] = f[k + 3][1] = 1;
FOR(i, 1, k + 2) fac[i] = fac[i - 1] * i;
FOR(i, 1, k + 2) f[i][0] = f[i - 1][0] * (n - i);
ROF(i, k + 2, 1) f[i][1] = f[i + 1][1] * (n - i);
FOR(i, 1, k + 2) s[i] = s[i - 1] + fp(i, k);
mint ans = 0;
FOR(i, 1, k + 2)
ans += s[i] * f[i - 1][0] * f[i + 1][1] / (fac[k + 2 - i] * fac[i - 1] * ((k - i) % 2 ? P - 1 : 1));
return ans;
}
void solve() {
int n, k;
cin >> n >> k;
mint ans = mint(k) * fp(k, n);
ans -= calc(k - 1, n);
ans /= fp(k, n);
cout << ans << endl;
}
[CrCPC 2024] F - 取名
直接模拟,时间复杂度 \(O(n)\)。
ケロシの代码
const int N = 1e6 + 5;
int n, a[N], vis[N];
void solve() {
cin >> n;
FOR(i, 1, n) cin >> a[i];
int c0 = 0, c1 = 0;
FOR(i, 1, n) {
vis[a[i]] = 1;
if(a[i] % 2) c1 ++;
else c0 ++;
}
int pos = 2 - (c1 > c0);
while(vis[pos]) pos += 2;
cout << pos << endl;
}
[CrCPC 2024] G - 传传爆
首先注意到进入任意一个白点都是一样的,所以策略一定是要么直接走到终点,要么走到最近的白点,每次决策是继续走白点还是直接走到终点。
首先把白点需要一步或两步再次走到白点,通过一步还是两步分为两类。
不难发现对于一类白点,存在一个阈值,使得到终点的距离小于这个阈值就直接走向终点,否则继续走白点。
根据数学直觉,不难发现两类的期望步数都是关于阈值单峰的,所以直接两次三分即可。
时间复杂度 \(O(nm)\)。
ケロシの代码
const int N = 1e3 + 5;
const int M = 1e6 + 5;
const int INF = 1e9 + 7;
ll gcd(ll x, ll y) {
if(! y) return x;
return gcd(y, x % y);
}
struct Fint {
ll x, y;
Fint(ll _x = 0, ll _y = 1) {
x = _x, y = _y;
ll g = gcd(x, y);
x /= g, y /= g;
}
friend Fint operator * (Fint A, Fint B) {
return Fint(A.x * B.x, A.y * B.y);
}
friend Fint operator / (Fint A, Fint B) {
return Fint(A.x * B.y, A.y * B.x);
}
friend Fint operator + (Fint A, Fint B) {
return Fint(A.x * B.y + A.y * B.x, A.y * B.y);
}
friend Fint operator - (Fint A, Fint B) {
return Fint(A.x * B.y - A.y * B.x, A.y * B.y);
}
friend ostream & operator << (ostream & os, Fint A) {
return os << A.x << '/' << A.y;
}
friend bool operator > (Fint A, Fint B) {
return A.x * B.y > A.y * B.x;
}
};
int n, m, dis;
int d[N][N];
string s[N];
int dx[] = {0, 0, 1, - 1};
int dy[] = {1, - 1, 0, 0};
bool b[N][N];
ll s1[M], s2[M];
int t1, t2, l1, l2;
Fint get(int i, int j) {
if(! i && ! j) return Fint(INF);
return Fint(s1[i] + s2[j] + t1 - i + (t2 - j) * 2, i + j) + dis;
}
Fint query(int i) {
Fint res(INF);
int L = 0, R = l2;
while(R - L >= 3) {
int mid = L + R >> 1;
if(get(i, mid) > get(i, mid + 1)) {
L = mid;
}
else {
R = mid;
}
}
FOR(j, L, R) chmin(res, get(i, j));
return res;
}
void solve() {
cin >> n >> m;
FOR(i, 1, n) {
cin >> s[i];
s[i] = ' ' + s[i];
}
FOR(i, 1, n) FOR(j, 1, m) d[i][j] = INF;
FOR(i, 1, n) FOR(j, 1, m) b[i][j] = 0;
FOR(x, 1, n) FOR(y, 1, m) if(s[x][y] == 'B') {
REP(i, 4) {
int px = x + dx[i];
int py = y + dy[i];
if(px < 1 || px > n || py < 1 || py > m) continue;
if(s[px][py] == 'B') b[x][y] = 1;
}
}
queue<PII> q;
d[n][m] = 0;
q.push({n, m});
t1 = t2 = l1 = l2 = 0;
while(! q.empty()) {
auto h = q.front();
q.pop();
int x, y; tie(x, y) = h;
if(s[x][y] == 'B') {
if(b[x][y]) s1[++ l1] = d[x][y];
else s2[++ l2] = d[x][y];
continue;
}
REP(i, 4) {
int px = x + dx[i];
int py = y + dy[i];
if(px < 1 || px > n || py < 1 || py > m) continue;
if(d[px][py] == INF) {
d[px][py] = d[x][y] + 1;
q.push({px, py});
}
}
}
FOR(i, 1, n) FOR(j, 1, m) if(s[i][j] == 'B') {
(b[i][j] ? t1 : t2) ++;
}
FOR(i, 1, l1) s1[i] += s1[i - 1];
FOR(i, 1, l2) s2[i] += s2[i - 1];
dis = INF;
FOR(i, 1, n) FOR(j, 1, m) if(s[i][j] == 'B')
chmin(dis, i + j - 2);
Fint ans(d[1][1]);
int L = 0, R = l1;
while(R - L >= 3) {
int mid = L + R >> 1;
if(query(mid) > query(mid + 1)) {
L = mid;
}
else {
R = mid;
}
}
FOR(i, L, R) chmin(ans, query(i));
cout << ans << endl;
}
[CrCPC 2024] H - 信步山中
按照题意模拟建图,然后跑 SPFA 即可。
时间复杂度 \(O(nm)\)。
ケロシの代码
const int N = 2e3 + 5;
const int M = 2e5 + 5;
const ll LNF = 1e18;
int n, st, ed;
int m1, fu1[M], fv1[M], fw1[M];
int m2, fu2[M], fv2[M], fw2[M];
vector<PII> e[N];
ll d1[N], d2[N]; bool vis[N];
ll d[N]; int c[N];
void clear() {
FOR(i, 1, n * 2) e[i].clear();
}
void add(int u, int v, int w) {
e[u].push_back({v, w});
}
void solve() {
cin >> n >> st >> ed;
cin >> m1;
FOR(i, 1, m1) cin >> fu1[i] >> fv1[i] >> fw1[i];
cin >> m2;
FOR(i, 1, m2) cin >> fu2[i] >> fv2[i] >> fw2[i];
FOR(i, 1, m1) {
add(fu1[i], fv1[i], fw1[i]);
add(fv1[i], fu1[i], fw1[i]);
}
priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> pq;
FOR(i, 1, n) d1[i] = LNF;
d1[ed] = 0;
pq.push({0, ed});
while(! pq.empty()) {
int u = SE(pq.top());
pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(auto h : e[u]) {
int v, w; tie(v, w) = h;
if(d1[v] > d1[u] + w) {
d1[v] = d1[u] + w;
pq.push({d1[v], v});
}
}
}
clear();
FOR(i, 1, m2) {
add(fu2[i], fv2[i], fw2[i]);
add(fv2[i], fu2[i], fw2[i]);
}
FOR(i, 1, n) d2[i] = LNF;
FOR(i, 1, n) vis[i] = 0;
d2[ed] = 0;
pq.push({0, ed});
while(! pq.empty()) {
int u = SE(pq.top());
pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(auto h : e[u]) {
int v, w; tie(v, w) = h;
if(d2[v] > d2[u] + w) {
d2[v] = d2[u] + w;
pq.push({d2[v], v});
}
}
}
clear();
FOR(i, 1, m1) {
if(d1[fu1[i]] > d1[fv1[i]]) add(fu1[i], fv1[i] + n, fw1[i]);
if(d1[fu1[i]] < d1[fv1[i]]) add(fv1[i], fu1[i] + n, fw1[i]);
}
FOR(i, 1, m2) {
if(d2[fu2[i]] > d2[fv2[i]]) add(fu2[i] + n, fv2[i], fw2[i]);
if(d2[fu2[i]] < d2[fv2[i]]) add(fv2[i] + n, fu2[i], fw2[i]);
}
queue<int> q;
FOR(i, 1, n * 2) d[i] = - LNF;
FOR(i, 1, n * 2) vis[i] = 0;
d[st] = 0;
q.push(st);
while(! q.empty()) {
int u = q.front();
q.pop(); vis[u] = 0;
for(auto h : e[u]) {
int v, w; tie(v, w) = h;
if(d[v] < d[u] + w) {
d[v] = d[u] + w;
if(! vis[v]) {
c[v] ++;
if(c[v] > n * 2) {
cout << - 1 << endl;
return;
}
vis[v] = 1;
q.push(v);
}
}
}
}
ll ans = max(d[ed], d[ed + n]);
cout << (ans == - LNF ? - 1 : ans) << endl;
}
[CrCPC 2024] I - 别样的滚榜大战
模拟,时间复杂度 \(O(n)\)。
ケロシの代码
const int N = 1e3 + 5;
int n, m;
ll b[N], a[N];
string name[N];
void solve() {
cin >> n >> m;
FOR(i, 1, n + 1) {
cin >> name[i];
REP(_, m) {
string s; cin >> s;
if(s[0] == '-') continue;
b[i] ++;
ll x = s[1] - '0';
ll H = (s[3] - '0') * 10 + (s[4] - '0');
ll M = (s[6] - '0') * 10 + (s[7] - '0');
ll S = (s[9] - '0') * 10 + (s[10] - '0');
ll val = S + (M + (x - 1) * 20) * 60 + H * 60 * 60;
a[i] += val;
}
}
int rk = 1;
FOR(i, 1, n) if(name[i] != name[n + 1]) {
if(b[i] > b[n + 1]) rk ++;
if(b[i] == b[n + 1]) {
if(a[i] < a[n + 1]) rk ++;
if(a[i] == a[n + 1]) {
if(name[i] < name[n + 1]) rk ++;
}
}
}
cout << rk << endl;
}
[CrCPC 2024] J - 搬东西
考虑答案会变成什么样子,设所有段的最左处是 \(L\),最右处是 \(R\)。
那么对于答案,首先有一个主方向,比如向右,那么存在点 \(x,y\) 满足 \(x<y\),那么主路线即为 \(x \to L \to R \to y\),那么所有向右的线段都能被经过。接下来还剩下一些没被经过的 \(r \to l\) 的向左的段,直接花费 \(2(r-l)\) 的代价即可。
直接枚举后贪心就这个东西即可。
时间复杂度 \(O(n \log n)\)。
ケロシの代码
const int N = 1e5 + 5;
const int INF = 1e9 + 7;
const ll LNF = 1e18;
int Len, n;
int a[N], b[N];
void solve() {
cin >> Len >> n;
FOR(i, 1, n) cin >> a[i] >> b[i];
ll ans = LNF;
REP(_, 2) {
int L = INF, R = - INF;
FOR(i, 1, n) chmin(L, a[i]), chmin(L, b[i]);
FOR(i, 1, n) chmax(R, a[i]), chmax(R, b[i]);
vector<PII> e, f;
FOR(i, 1, n) if(a[i] > b[i]) {
e.push_back({b[i], 1});
e.push_back({a[i], - 1});
}
sort(begin(e), end(e));
int val = 0, pl = 0;
f.push_back({L, L});
for(auto h : e) {
if(val == 0) pl = FI(h);
val += SE(h);
if(val == 0) f.push_back({pl, FI(h)});
}
f.push_back({R, R});
ll res = L;
for(auto h : f) {
chmin(ans, 2ll * (R - L) + res - FI(h));
res = min(res + 2ll * (SE(h) - FI(h)), (ll) SE(h));
}
FOR(i, 1, n) a[i] = - a[i], b[i] = - b[i];
}
cout << ans << endl;
}
[CrCPC 2024] K - 牙牙学语
考虑建出字典树,考虑有两个点从根开始走,一直走相同的字符,如果遇到了结束位置就可以回到根,若两个点同时对应了两个终点,则不合法。所以直接在字典树上 dfs 即可,同时记录一下走过的位置防止重复经过。
时间复杂度 \(O(t^2)\)。
ケロシの代码
const int N = 1e6 + 5;
int tr[N][26], ed[N], idx;
bool f[N][26], ok;
void clear() {
FOR(i, 0, idx) {
REP(j, 26) f[i][j] = tr[i][j] = 0;
ed[i] = 0;
}
idx = 0;
}
void dfs(int u, int v) {
if(ok) return;
if(u == v && ed[u] >= 2) {
ok = 1;
return;
}
if(u != v && ed[u] && ed[v]) {
ok = 1;
return;
}
REP(i, 26) {
if(u == tr[0][i] && f[v][i]) return;
if(v == tr[0][i] && f[u][i]) return;
}
REP(i, 26) {
if(u == tr[0][i]) f[v][i] = 1;
if(v == tr[0][i]) f[u][i] = 1;
}
REP(i, 26) if(tr[u][i] && tr[v][i]) dfs(tr[u][i], tr[v][i]);
REP(i, 26) if(ed[u] && tr[0][i] && tr[v][i]) dfs(tr[0][i], tr[v][i]);
REP(i, 26) if(tr[u][i] && ed[v] && tr[0][i]) dfs(tr[u][i], tr[0][i]);
}
void solve() {
clear();
REP(i, 26) {
string s; cin >> s;
int u = 0;
for(char c : s) {
if(! tr[u][c - 'a']) tr[u][c - 'a'] = ++ idx;
u = tr[u][c - 'a'];
}
ed[u] ++;
}
ok = 0;
dfs(0, 0);
cout << (ok ? "Ruzan" : "Krasan") << endl;
}