CSP-S 模拟赛34
CSP-S 模拟赛34
T1
考虑对原序列将 \(k\) 的左右分成两个序列。simple 的想法是分别从 \(1\) 开始跑前缀和,每一次总跑到下一个小于它的点,然后依次类推。发现这样做碰到序列最小值之后难以继续。
然而我们发现这样跑点的过程从前往后和从后往前是等价的。这样考虑的原因是发现这样的选数问题不具有方向性。于是时间复杂度 \(O(n)\)。
代码:
#include <bits/stdc++.h>
#define N 100005
#define int long long
using namespace std;
int T;
int n, k;
int a[N];
int a1[N], a2[N];
int ct1, ct2;
int sm1[N], sm2[N];
int nx1[N], nx2[N];
void sve() {
memset(sm1, 0, sizeof sm1);
memset(sm2, 0, sizeof sm2);
memset(nx1, 0, sizeof nx1);
memset(nx2, 0, sizeof nx2);
cin >> n >> k;
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
ct1 = ct2 = 1;
a1[1] = a2[1] = 0;
for (int i = k; i > 1; i--)
a1[++ct1] = a[i];
for (int i = k + 1; i <= n; i++)
a2[++ct2] = a[i];
for (int i = 1; i <= ct1; i++)
sm1[i] = sm1[i - 1] + a1[i];
for (int i = 1; i <= ct2; i++)
sm2[i] = sm2[i - 1] + a2[i];
int mn = sm1[1], nw = 1;
for (int i = 2; i <= ct1; i++)
if (sm1[i] <= mn) {
nx1[nw] = i;
nw = i;
mn = sm1[i];
}
mn = sm1[ct1], nw = ct1;
for (int i = ct1 - 1; i >= 1; i--)
if (sm1[i] < mn) {
nx1[nw] = i;
nw = i;
mn = sm1[i];
}
mn = sm2[1], nw = 1;
for (int i = 2; i <= ct2; i++)
if (sm2[i] <= mn) {
nx2[nw] = i;
nw = i;
mn = sm2[i];
}
mn = sm2[ct2], nw = ct2;
for (int i = ct2 - 1; i >= 1; i--)
if (sm2[i] < mn) {
nx2[nw] = i;
nw = i;
mn = sm2[i];
}
if (sm1[ct1] + sm2[ct2] > 0)
return puts("No"), void();
int p1 = 1, p2 = 1;
while (nx1[p1] || nx2[p2]) {
if (!nx1[p1]) {
for (int j = p2 + 1; j <= nx2[p2]; j++)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
continue;
}
if (!nx2[p2]) {
for (int j = p1 + 1; j <= nx1[p1]; j++)
if (sm1[j] + sm2[p2] > 0) {
puts("No");
return;
}
p1 = nx2[p1];
continue;
}
int fg = 0;
for (int i = p1 + 1; i <= nx1[p1]; i++)
if (sm1[i] + sm2[p2] > 0) {
fg = 1;
break;
}
if (!fg) {
p1 = nx1[p1];
continue;
}
for (int j = p2 + 1; j <= nx2[p2]; j++)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
}
p1 = ct1, p2 = ct2;
while (nx1[p1] || nx2[p2]) {
if (!nx1[p1]) {
for (int j = p2 - 1; j >= nx2[p2]; j--)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
continue;
}
if (!nx2[p2]) {
for (int j = p1 - 1; j >= nx1[p1]; j--)
if (sm1[j] + sm2[p2] > 0) {
puts("No");
return;
}
p1 = nx2[p1];
continue;
}
int fg = 0;
for (int i = p1 - 1; i >= nx1[p1]; i--)
if (sm1[i] + sm2[p2] > 0) {
fg = 1;
break;
}
if (!fg) {
p1 = nx1[p1];
continue;
}
for (int j = p2 - 1; j >= nx2[p2]; j--)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
}
puts("Yes");
}
signed main() {
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
cin >> T;
for (int i = 1; i <= T; i++)
sve();
return 0;
}
T2
显然考虑 \(O(n^2)\) 的 dp。
朴素的 dp 定义是 \(dp_{i,j}\) 表示长度为 \(i\) 的序列,\(j\) 次消除的方案数。然而发现这样转移的复杂度难以接受,需要分别枚举左右区间的消除次数。
考虑某一个位置 \(x\) 的消除次数由什么决定。对于只有某一边有 \(>a_x\) 的,则这个位置的消除次数一定是 \(j-1\)。对于两边都有 \(>a_x\) 的,两边的消除次数有一个是 \(j-1\)。那么就考虑前缀和优化这个 dp,则定义 \(dp_{i,j,0/1}\) 表示长度为 \(i\) 的序列,至多 \(j\) 次消除,有一边 / 两边 \(>a_x\) 的方案数,那么 \(j\) 便由 \(j,j-1\) 转移而来。
时间复杂度大抵是 \(O(n^2\log^2n)\)。
这样考虑的原因是注意到每个最大值都会覆盖一个 "区间",因此可以由左右两端转移而来。
代码:
#include <bits/stdc++.h>
#define N 1005
#define int long long
using namespace std;
int n, k, p;
int C[N][N];
int dp[N][N][2];
void add(int &x, int y) {
x = (x + y + p) % p;
}
signed main() {
freopen("per.in", "r", stdin);
freopen("per.out", "w", stdout);
cin >> n >> k >> p;
C[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= i; j++)
C[i][j] = (C[i - 1][j] + (j > 0 ? C[i - 1][j - 1] : 0)) % p;
for (int i = 0; i <= k; i++)
dp[0][i][0] = dp[0][i][1] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= k; j++)
for (int t = 1; t <= i; t++) {
add(dp[i][j][0], C[i - 1][t - 1] * dp[t - 1][j - 1][1] % p * dp[i - t][j][0] % p);
add(dp[i][j][1], C[i - 1][t - 1] * ((dp[t - 1][j][1] * dp[i - t][j - 1][1] % p + dp[t - 1][j - 1][1] * dp[i - t][j][1] % p - dp[t - 1][j - 1][1] * dp[i - t][j - 1][1] % p) % p) % p);
}
int ans = 0;
for (int i = 1; i <= n; i++)
add(ans, C[n - 1][i - 1] * ((dp[i - 1][k][0] * dp[n - i][k][0] % p - dp[i - 1][k - 1][0] * dp[n - i][k - 1][0] % p)) % p % p);
cout << ans << "\n";
return 0;
}
T3
考虑问题的本质是从 \(1\rightarrow n\) 再从 \(n\rightarrow 1\),显然贪心地维护不具有正确性,于是我们可以维护一个点对 \((x,y)\) 表示 \(1\rightarrow x,y\rightarrow 1\) 的最小值,所求就是 \((n,n)\) 的答案。实现的时候可以建反图,用类似 Dijkstra 的方法动态维护哪些点走过。
对于正确性,考虑不用买门票的情形一定是去对回做贡献或是回对去做贡献,那这样的枚举是保证了某一方向先走到一个点再更新另一个方向,于是必然可以更新到正确答案。
代码:
#include <bits/stdc++.h>
#define N 255
using namespace std;
int n, m;
struct MP {
struct Node {
int to, nxt;
} e[N * N];
int head[N], cnt;
void add(int u, int v) {
e[++cnt].to = v;
e[cnt].nxt = head[u];
head[u] = cnt;
}
} A, B;
int vl[N];
int dis[N][N];
bitset<N>can[N][N];
struct node {
int x, y;
int dis;
bool operator < (const node &x) const {
return dis > x.dis;
}
};
priority_queue<node>q;
int vis[N][N];
void Dij() {
dis[1][1] = vl[1];
can[1][1].set(1);
q.push((node) {
1, 1, dis[1][1]
});
while (!q.empty()) {
node tmp = q.top();
q.pop();
int x = tmp.x, y = tmp.y;
if (vis[x][y])
continue;
vis[x][y] = 1;
for (int i = A.head[x]; i; i = A.e[i].nxt) {
int p = A.e[i].to;
int ds = tmp.dis;
if (!can[x][y][p])
ds += vl[p];
if (ds < dis[p][y]) {
dis[p][y] = ds;
can[p][y] = can[x][y];
can[p][y].set(p);
q.push((node) {
p, y, dis[p][y]
});
}
}
for (int i = B.head[y]; i; i = B.e[i].nxt) {
int p = B.e[i].to;
int ds = tmp.dis;
if (!can[x][y][p])
ds += vl[p];
if (ds < dis[x][p]) {
dis[x][p] = ds;
can[x][p] = can[x][y];
can[x][p].set(p);
q.push((node) {
x, p, dis[x][p]
});
}
}
}
}
int main() {
freopen("tour.in", "r", stdin);
freopen("tour.out", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= n; i++)
scanf("%d", &vl[i]);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
A.add(x, y);
B.add(y, x);
}
memset(dis, 0x3f, sizeof dis);
Dij();
if (dis[n][n] < 0x3f3f3f3f)
cout << dis[n][n] << "\n";
else
puts("-1");
return 0;
}
T4
其实是简单题。矩形问题套路地考虑扫描线。将矩形按照 \(y_1\) 排序,线段树维护 \(x\) 维度。考虑用 \(y_1\) 和下面的矩形建立联系,用 \(y_2\) 更新区间的上边。建立联系考虑并查集维护。对于一个区间,显然只有原先只被一个矩形覆盖的时候可以直接更新,否则要继续分治直到只被单个矩形覆盖为止。
对于时间复杂度,更新上边的操作单次显然是 \(O(\log n)\) 的,对于合并,"拆分" 一个区间的代价是对新的区间合并,均摊下来是 \(O(n\log n)\) 的,于是 \(o(n\log n\alpha(n))\)。
代码:
#include <bits/stdc++.h>
#define N 100005
#define M 100000
using namespace std;
int n;
int fa[N];
int fnd(int x) {
return x == fa[x] ? x : (fa[x] = fnd(fa[x]));
}
void mge(int x, int y) {
x = fnd(x), y = fnd(y);
if (x != y)
fa[x] = y;
}
struct Node {
int l, r;
int mx;
int fg;
int tp;
int id;
} e[N << 2];
#define lc (p << 1)
#define rc (lc | 1)
#define l(i) e[i].l
#define r(i) e[i].r
#define mx(i) e[i].mx
#define fg(i) e[i].fg
#define tp(i) e[i].tp
#define id(i) e[i].id
void push_up(int p) {
if (!tp(lc) || !tp(rc))
return tp(p) = 0, void();
if (id(lc) == id(rc)) {
tp(p) = 1;
mx(p) = mx(lc);
id(p) = id(lc);
}
else
tp(p) = 0;
}
void build(int p, int l, int r) {
l(p) = l, r(p) = r;
tp(p) = 1;
if (l == r)
return;
int mid = (l + r) >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
}
void push_down(int p) {
if (fg(p) == 0)
return;
fg(lc) = fg(rc) = 1;
mx(lc) = mx(rc) = mx(p);
id(lc) = id(rc) = id(p);
tp(lc) = tp(rc) = 1;
fg(p) = 0;
}
void update(int p, int l, int r, int mx, int id) {
if (l > r || l > r(p) || l(p) > r)
return;
if (l <= l(p) && r(p) <= r && tp(p)) {
if (mx(p) > mx)
return;
fg(p) = 1;
mx(p) = mx;
id(p) = id;
return;
}
push_down(p);
update(lc, l, r, mx, id);
update(rc, l, r, mx, id);
push_up(p);
}
void query(int p, int l, int r, int mx, int id) {
if (l > r || l > r(p) || l(p) > r)
return;
if (l <= l(p) && r(p) <= r && tp(p)) {
if (mx(p) >= mx)
mge(id(p), id);
return;
}
push_down(p);
query(lc, l, r, mx, id);
query(rc, l, r, mx, id);
}
struct node {
int x1, x2, y1, y2;
bool operator < (const node &x) const {
return y1 < x.y1;
}
} t[N];
int main() {
freopen("jux.in", "r", stdin);
freopen("jux.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++)
scanf("%d%d%d%d", &t[i].x1, &t[i].y1, &t[i].x2, &t[i].y2), fa[i] = i;
sort(t + 1, t + 1 + n);
build(1, 1, M);
for (int i = 1; i <= n; i++) {
query(1, t[i].x1, t[i].x2, t[i].y1, i);
update(1, t[i].x1, t[i].x2, t[i].y2, i);
}
unordered_map<int, int>mp;
for (int i = 1; i <= n; i++)
mp[fnd(i)] = 1;
cout << mp.size() << "\n";
return 0;
}

浙公网安备 33010602011771号