[赛记] 多校A层冲刺NOIP2024模拟赛01【衡中】
构造字符串 50pts
错解50pts;
考虑正解,对于题目中的要求,我们可以转换成若干个相等与不等的操作,若相等则用并查集合并一下,不等则连边,若同块连边则无解,否则从前往后遍历赋值,每次找所连边其它块值的 $ \operatorname{mex} $ 即可;
时间复杂度:$ \Theta(nm \alpha(n)) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
int n, m;
int fa[5005];
int find(int x) {
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
struct sss{
int x, y, z;
}e[5005];
int cnt;
vector<int> v[5005];
int ans[5005];
bool vis[5005];
int main() {
freopen("str.in", "r", stdin);
freopen("str.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) fa[i] = i;
int cnt = m;
memset(ans, -1, sizeof(ans));
for (int i = 1; i <= m; i++) {
cin >> e[i].x >> e[i].y >> e[i].z;
if (e[i].x > e[i].y) swap(e[i].x, e[i].y);
if (e[i].z != 0) {
int l = e[i].x;
int r = e[i].y;
int o = e[i].z;
while(o) {
fa[find(l)] = find(r);
l++;
r++;
o--;
}
if (r <= n) {
e[++cnt] = {l, r, 0};
}
} else {
e[++cnt] = e[i];
}
}
for (int i = m + 1; i <= cnt; i++) {
int l = e[i].x;
int r = e[i].y;
if (find(l) == find(r)) {
cout << -1;
return 0;
}
v[find(l)].push_back(find(r));
v[find(r)].push_back(find(l));
}
for (int i = 1; i <= n; i++) {
int x = find(i);
if (ans[x] != -1) continue;
for (int j = 0; j <= n; j++) vis[j] = false;
for (int j = 0; j < v[x].size(); j++) {
if (ans[v[x][j]] != -1) vis[ans[v[x][j]]] = true;
}
for (int j = 0; j <= n; j++) {
if (!vis[j]) {
ans[x] = j;
break;
}
}
}
for (int i = 1; i <= n; i++) {
cout << ans[find(i)] << ' ';
}
return 0;
}
寻宝 100pts
T2放纯搜,也是没谁了;
首先跑一边 $ BFS $ 将连通块缩点,然后在传送门所连接的两个连通块中连单向边,最后从每个人出发点所处连通块 $ BFS $,看看能不能到达终点所处连通块;
时间复杂度:$ \Theta(kq) $;
注意本题并没有具体地给出 $ n, m $ 的范围,所以要给每个点标号,而且输入图的时候要用 vector
;
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
int n, m, k, q;
vector<char> s[500005];
char c;
bool vis[500005], vi[500005];
int rem[500005];
int belog[500005], sum;
int id(int x, int y) {
return (x - 1) * m + y;
}
bool in(int x, int y) {
return x >= 1 && x <= n && y >= 1 && y <= m;
}
int a[5] = {-1, 0, 0, 1};
int b[5] = {0, 1, -1, 0};
void bfs(int x, int y, int now) {
queue<pair<int, int> > q;
while(!q.empty()) q.pop();
q.push({x, y});
belog[id(x, y)] = now;
vis[id(x, y)] = true;
while(!q.empty()) {
int xx = q.front().first;
int yy = q.front().second;
q.pop();
for (int i = 0; i <= 3; i++) {
int nx = xx + a[i];
int ny = yy + b[i];
if (in(nx, ny) && s[nx][ny] != '#' && !vis[id(nx, ny)]) {
vis[id(nx, ny)] = true;
belog[id(nx, ny)] = now;
q.push({nx, ny});
}
}
}
}
struct sss{
int t, ne;
}e[500005];
int h[500005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
bool afs(int s, int t) {
if (s == t) return true;
rem[0] = 0;
queue<int> q;
while(!q.empty()) q.pop();
q.push(s);
vi[s] = true;
rem[++rem[0]] = s;
while(!q.empty()) {
int x = q.front();
q.pop();
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == t) return true;
if (!vi[u]) {
vi[u] = true;
rem[++rem[0]] = u;
q.push(u);
}
}
}
return false;
}
int main() {
freopen("treasure.in", "r", stdin);
freopen("treasure.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> k >> q;
for (int i = 1; i <= n; i++) s[i].push_back('0');
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> c;
s[i].push_back(c);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!vis[id(i, j)] && s[i][j] != '#') bfs(i, j, ++sum);
}
}
int x1, x2, y1, y2;
for (int i = 1; i <= k; i++) {
cin >> x1 >> y1 >> x2 >> y2;
if (belog[id(x1, y1)] != belog[id(x2, y2)]) add(belog[id(x1, y1)], belog[id(x2, y2)]);
}
for (int i = 1; i <= q; i++) {
cin >> x1 >> y1 >> x2 >> y2;
if (afs(belog[id(x1, y1)], belog[id(x2, y2)])) cout << 1 << '\n';
else cout << 0 << '\n';
for (int j = 1; j <= rem[0]; j++) vi[rem[j]] = false;
}
return 0;
}
序列 40pts
暴力40pts;
对于正解,首先维护出 $ a $ 和 $ b $ 的前缀和 $ suma $ 和 $ sumb $,然后对于一段区间,我们给它分成 $ [l, p], [p + 1, r] $ 两段子区间,然后分别取 $ \max $ 相加即可(比较人类智慧,不过还好);
对于 $ [p + 1, r] $ ,我们要求:
也就是:
将 $ k $ 看作自变量 $ x $,发现后面的式子是关于 $ x $ 的一次函数,我们只需找出其当 $ x = k $ 时的最大值即可,这个用李超线段树是很好维护的;
左区间同理,式子推出来后发现只不过是把斜率和截距都取反了,所以我们维护一个最小值即可;
好像也可以维护凸包(类似斜率优化),但我不会;
注意直线有限制,所以我们要离线对其进行排序再插入;
发现我们要插入的直线条数是 $ n $ 条,而且只是插入直线,单点查询,所以时间复杂度为 $ \Theta(n \log n) $,常数较大,开 $ 1e6 $ 跑得较慢,空间也比较极限;
板子自己造的,所以有些冗长;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m;
long long a[1000005], b[1000005], suma[1000005], sumb[1000005];
long long ansl[1000005], ansr[1000005];
struct sss{
long long p, k;
int id;
inline bool operator <(const sss &A) const {
return p < A.p;
}
}q[1000005];
inline bool cmp(sss x, sss y) {
return x.p > y.p;
}
inline bool cmpx(sss x, sss y) {
return x.id < y.id;
}
namespace LCSEG{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r;
long long k, b;
}tr[8000005];
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
tr[id].k = 1e18;
tr[id].b = 1e18;
if (l == r) return;
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
}
void add(int s, int id, long long k, long long b) {
if (tr[id].k == 1e18) {
tr[id].k = k;
tr[id].b = b;
if (tr[id].l == tr[id].r) return;
add(s, ls(id), k, b);
add(s, rs(id), k, b);
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (s == 0) {
long long mtr = tr[id].k * mid + tr[id].b;
long long mli = k * mid + b;
if (mtr < mli) {
swap(tr[id].k, k);
swap(tr[id].b, b);
}
long long ltr = tr[id].k * tr[id].l + tr[id].b;
long long lli = k * tr[id].l + b;
long long rtr = tr[id].k * tr[id].r + tr[id].b;
long long rli = k * tr[id].r + b;
if (ltr < lli) add(s, ls(id), k, b);
else if (rtr < rli) add(s, rs(id), k, b);
} else if (s == 1) {
long long mtr = tr[id].k * mid + tr[id].b;
long long mli = k * mid + b;
if (mtr > mli) {
swap(tr[id].k, k);
swap(tr[id].b, b);
}
long long ltr = tr[id].k * tr[id].l + tr[id].b;
long long lli = k * tr[id].l + b;
long long rtr = tr[id].k * tr[id].r + tr[id].b;
long long rli = k * tr[id].r + b;
if (ltr > lli) add(s, ls(id), k, b);
else if (rtr > rli) add(s, rs(id), k, b);
}
}
long long ask(int s, int id, long long x) {
if (tr[id].l == tr[id].r) return tr[id].k * x + tr[id].b;
int mid = (tr[id].l + tr[id].r) >> 1;
long long val = tr[id].k * x + tr[id].b;
if (s == 0) {
if (x <= mid) return max(val, ask(s, ls(id), x));
else return max(val, ask(s, rs(id), x));
} else {
if (x <= mid) return min(val, ask(s, ls(id), x));
else return min(val, ask(s, rs(id), x));
}
}
}
using namespace LCSEG;
int main() {
freopen("seq.in", "r", stdin);
freopen("seq.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
suma[i] = suma[i - 1] + a[i];
sumb[i] = sumb[i - 1] + b[i];
}
bt(1, -1000000, 1000000);
for (int i = 1; i <= m; i++) {
cin >> q[i].p >> q[i].k;
q[i].id = i;
}
sort(q + 1, q + 1 + m);
int ls = 0;
for (int i = 1; i <= m; i++) {
for (int j = ls; j < q[i].p; j++) {
add(1, 1, -sumb[j], suma[j]);
}
ansl[q[i].id] = ask(1, 1, q[i].k) * (-1);
ls = q[i].p;
}
sort(q + 1, q + 1 + m, cmp);
ls = n - 1;
bt(1, -1000000, 1000000);
add(0, 1, -sumb[n], suma[n]);
for (int i = 1; i <= m; i++) {
for (int j = ls; j > q[i].p; j--) {
add(0, 1, -sumb[j], suma[j]);
}
ansr[q[i].id] = ask(0, 1, q[i].k);
ls = q[i].p;
}
sort(q + 1, q + 1 + m, cmpx);
for (int i = 1; i <= m; i++) {
cout << max(ansl[i] + ansr[i], suma[q[i].p] - q[i].k * sumb[q[i].p] + ansl[i]) << '\n';
}
return 0;
}