2025多校CSP模拟赛1
2025多校CSP模拟赛1
开 T1 水,开 T2 发现能乱搞,搞完发现是正确的。
开 T3 发现是熟悉的 dp,马上开写一个插板。
写了 2h 后发现占地面积不好算,放弃了。
T1 交友
发现只要特判类似
CG
GC
即可。
code
// ubsan: undefined
// accoders
#include <iostream>
#include <queue>
using namespace std;
#define emp emplace_back
const int N = 1000 + 10, M = 1e5, K = 20;
#define fi first
#define se second
// #define int long long
using pii = pair <int, int>;
using ll = long long;
char a[N][N];
int ans = 0, cnt[N][N][3];
signed main()
{
freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
freopen("friend.in", "r", stdin); freopen("friend.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> a[i][j];
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
{
if (a[i][j] != 'G') continue;
int s = (a[i - 1][j] == 'C') + (a[i][j - 1] == 'C') + (a[i + 1][j] == 'C') + (a[i][j + 1] == 'C');
cnt[i][j][1] = (a[i][j - 1] == 'C') && (a[i + 1][j] == 'C');
cnt[i][j][2] = (a[i][j + 1] == 'C') && (a[i + 1][j] == 'C');
cnt[i][j][0] += s * (s - 1) / 2 - cnt[i][j][1] - cnt[i][j][2];
if (cnt[i][j][0])
{
++ans;
continue;
}
if (!cnt[i][j][1] && !cnt[i][j][2]) continue;
if (!cnt[i][j][1])
{
++ans;
cnt[i + 1][j + 1][0]--;
}
else if (!cnt[i][j][2])
{
++ans;
cnt[i + 1][j - 1][0]--;
}
else
{
cerr << "?";
}
}
cout << ans << '\n';
return 0;
}
T2 炼金
因为环一定不优,所以出现环直接爆了,但是感觉写 \(dfs\) 很没有前途,于是选择递推模拟这样的过程。
具体的每次二分一个答案,然后进行很多轮,每轮遍历数组如果欠了金属就找两个儿子要。
容易发现如果 \(O(n)\) 轮后还是还不上,就说明出现了环,此时直接判凑不成。
那么复杂度是 \(O(Tn^2\log V)\)
code
// ubsan: undefined
// accoders
#include <iostream>
#include <queue>
using namespace std;
#define emp emplace_back
const int N = 1000 + 10, M = 1e5, K = 20;
#define fi first
#define se second
#define int long long
using pii = pair<int, int>;
using ll = long long;
int n, d[N], ls[N], rs[N], tmpd[N];
signed main() {
freopen("data.in", "r", stdin);
freopen("ans.out", "w", stdout);
freopen("alchemy.in", "r", stdin);
freopen("alchemy.out", "w", stdout);
ios ::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T;
cin >> T;
for (int ajdkfjjflsjl = 1; ajdkfjjflsjl <= T; ajdkfjjflsjl++) {
int s = 0;
cin >> n;
for (int i = 1; i <= n; i++) cin >> ls[i] >> rs[i];
for (int i = 1; i <= n; i++) cin >> d[i], s += d[i], tmpd[i] = d[i];
int l = 0, r = s, pos = 0;
while (l <= r) {
int mid = (l + r) >> 1, tot = 0;
d[1] -= mid;
while (1) {
bool flag = 1;
++tot;
for (int i = 1; i <= n; i++) {
if (d[i] < -s) {
tot = 0x3f3f3f3f;
break;
}
if (d[i] < 0) {
flag = 0;
int t = d[i];
d[i] = 0;
d[ls[i]] += t, d[rs[i]] += t;
}
}
if (flag)
break;
if (tot > 20)
break;
}
bool ch = 1;
for (int i = 1; i <= n; i++) ch &= (d[i] >= 0);
if (ch)
pos = mid, l = mid + 1;
else
r = mid - 1;
for (int i = 1; i <= n; i++) d[i] = tmpd[i];
}
cout << "Case #" << ajdkfjjflsjl << ": " << pos << '\n';
}
return 0;
}
T3 磁铁
发现肯定是先让所有磁铁紧密排列,然后求出剩下的空位进行插板法。
但是左右端点的磁力占地面积是不全的,不过根本没必要枚举左右端点,只需要将占地面积压入 \(dp\) 状态就好。
其次我们发现当一个磁铁需要放入两个磁铁之间时,需要将这两个磁铁分开,这样贡献不好算。
那么就可以枚举当前分为多少个连通块,这样每个联通块的左右端点的磁力就不用考虑了,此时如果从小到大枚举,占地面积的增加就极好算了。
设 \(dp_{i,j,k}\) 表示当前枚举到 \(i\) 分为 \(j\) 个连通块,占地面积 \(k\)。
第 \(i\) 个单开一个连通块:\(dp_{i,j,k}+=j\times dp_{i-1,j-1,k-1}\)
第 \(i\) 个合并两个连通块:\(dp_{i,j,k}+=j\times dp_{i-1,j+1,k-(2r_i-1)}\)
第 \(i\) 个放在一个连通块的左端或右端:\(dp_{i,j,k}+=2j\times dp_{i-1,j,k-r_i}\)
答案就是简单插板法,\(dp_{n,1,i}C^{l-i+n}_{n}\)。
复杂度 \(O(n^2l)\)
code
// ubsan: undefined
// accoders
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
#define emp emplace_back
const int N = 60, M = 1e4 + 100, mod = 1e9 + 7;
#define fi first
#define se second
#define int long long
using pii = pair<int, int>;
using ll = long long;
int dp[N][N][M], r[N], n, l, C[M][N];
signed main() {
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
freopen("magnet.in", "r", stdin);
freopen("magnet.out", "w", stdout);
ios ::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> l;
for (int i = 1; i <= n; i++) cin >> r[i];
sort(r + 1, r + 1 + n);
dp[0][0][0] = 1;
C[0][0] = 1;
for (int i = 1; i <= l + n; i++) {
C[i][0] = 1;
for (int j = 1; j <= n; j++) {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
for (int i = 1; i <= n; i++) // now pos
{
for (int j = 1; j <= n; j++) // j SCC
{
for (int k = 1; k <= l; k++) // not empty size
{
dp[i][j][k] += dp[i - 1][j - 1][k - 1] * j; // new
if (k >= r[i])
dp[i][j][k] += dp[i - 1][j][k - r[i]] * 2 * j; // PushBack or PushFront
if (k >= 2 * r[i] - 1)
dp[i][j][k] += j * dp[i - 1][j + 1][k - 2 * r[i] + 1]; // Merge
dp[i][j][k] %= mod;
}
}
}
int ans = 0;
// cerr << dp[2][2][2] << ' ';
for (int i = 0; i <= l; i++) {
// cerr << dp[n][1][i] << ' ';
ans = (ans + dp[n][1][i] * C[l - i + n][n]) % mod;
}
cout << ans << '\n';
return 0;
}
T4 铁轨
很好的欧拉回路题。
考虑可以将问题转化为加速不消耗代价,减速消耗代价。
那么因为要经过所有的铁轨,那么这些边一定经过。
此时如果将每个速度抽象成一个点,并且加入一个从 \(inf\) 连向 \(1\) 个边,这样就不用考虑起点终点,由路径变为回路。
因为向左经过一个点和向右经过一个点的数量一定要相同,那么就以此为代价在 \(i\) 和 \(i+1\) 间连边。
可是有可能连完后不连通,那么可以上一个最小生成树维护一下。
code
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
#define emp emplace_back
const int N = 1e6 + 10, K = 102, mod = 1e9 + 7;
#define fi first
#define se second
#define int long long
using pii = pair <int, int>;
using ll = long long;
const int inf = 0x3f3f3f3f;
int f[N], s[N], t[N], a[N << 2], sum[N << 2], L[N], R[N];
int Find(int x)
{
if (f[x] == x) return f[x];
return f[x] = Find(f[x]);
}
void Merge(int x, int y)
{
int xx = Find(x), yy = Find(y);
if (xx == yy) return ;
f[xx] = yy;
L[yy] = min(L[yy], L[xx]);
R[yy] = max(R[yy], R[xx]);
}
struct Node
{
int from, to, w;
}e[N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
// freopen("rail.in", "r", stdin); freopen("rail.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> s[i] >> t[i];
a[i] = s[i], a[i + n] = t[i];
}
a[2 * n + 1] = a[2 * n + 2] = s[n + 1] = t[n + 1] = 1;
++n;
sort(a + 1, a + 1 + n * 2);
int cnt = unique(a + 1, a + 1 + n * 2) - (a + 1);
for (int i = 1; i <= cnt; i++) f[i] = L[i] = R[i] = i;
for (int i = 1; i <= n; i++)
{
s[i] = lower_bound(a + 1, a + 1 + cnt, s[i]) - a;
t[i] = lower_bound(a + 1, a + 1 + cnt, t[i]) - a;
sum[s[i]]++, sum[t[i]]--;
Merge(s[i], t[i]);
}
sum[1]--;
for (int i = 1; i <= cnt; i++) sum[i] = sum[i - 1] + sum[i];
int ans = 0;
for (int i = 2; i <= cnt; i++)
{
ans += max(0ll, (a[i] - a[i - 1]) * sum[i - 1]);
}
int tot = 0;
for (int i = 2; i <= cnt; i++)
{
if (sum[i - 1] != 0) Merge(i, i - 1);
if (Find(i) != Find(i - 1)) e[++tot] = {i - 1, i, a[i] - a[i - 1]};
}
sort(e + 1, e + 1 + tot, [](Node x, Node y) {return x.w < y.w;});
for (int i = 1; i <= tot; i++)
{
int x = Find(e[i].from), y = Find(e[i].to);
if (x == y) continue;
f[x] = y;
ans += e[i].w;
}
cout << ans;
return 0;
}