2022NOIP A层联测22 极源流体 等差数列 送给好友的礼物 非常困难的压轴题
[最小生成树/坐标系转图论]T1:给出一个矩形长n宽m,1代表黑色,0代表白色,你每次可以选择从上下左右4个方向的一个方向,以及参数K,把从任一个黑色格子出发的这么多长度染成黑色,求最少\(\sum_{}^{}K\)使得所有黑色格子八联通(可以只挨着不覆盖)。(n,m<=700)
考场
发现一次操作实际上是覆盖了一个矩阵空间,4种操作可以等效成只有下和右的操作,但是不知道怎么正确地在确定操作后判断合法,于是挂了-->10pts
暴力35pts(打的好的有79分)
可以处理出b数组表示在合法范围内的位置,差分序列或者预处理矩形覆盖都可以,把任意一个点放入dequeBFS一下,如果能够在合法移动范围内遍历所有黑点就是可以的。然后发现x和y具有单调性,O(n+m)移动一下,\(O(n^3)\)的算法T了。。。
还有一些无关痛痒的剪枝,比如按照x递减,y增,这样如果y不合法就直接break了,因为越x小越不可能合法,\(ans<=y也break\),因为y是增的。
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register int
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 733;
bool a[N][N], v[N][N];
int b[N][N];
int dx[] = { 0, 0, 1, 1, 1, -1, -1, -1 };
int dy[] = { 1, -1, 0, 1, -1, 0, 1, -1 };
int n, m;
vector<pair<int, int> > g; //存黑点位置
int spx, spy;
char s[N];
deque<pair<int, int> > q;
inline void get(int x1, int y1, int x2, int y2) {
// chu("check(%d %d)(%d %d)\n",x1,y1,x2,y2);
b[x1][y1]++;
b[x2 + 1][y2 + 1]++;
b[x1][y2 + 1]--;
b[x2 + 1][y1]--;
}
inline int Min(int x, int y) { return (x < y) ? (x) : y; }
inline bool check(int S, int H) //竖着和横着
{
// chu("Try:%d %d\n",S,H);
q.clear();
for (rint i = 0; i <= n; ++i)
for (rint j = 0; j <= m; ++j) v[i][j] = b[i][j] = 0;
for (pair<int, int> ele : g) get(ele.first, ele.second, Min(ele.first + S, n), Min(ele.second + H, m));
for (rint i = 1; i <= n; ++i)
for (rint j = 1; j <= m; ++j) b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1] + b[i][j];
int tot = 0;
v[spx][spy] = 1;
q.push_back(make_pair(spx, spy));
while (!q.empty()) {
// chu("out:%d\n",q.front().first);
int x = q.front().first, y = q.front().second;
q.pop_front();
if (a[x][y])
++tot;
if (tot >= g.size())
return 1;
for (rint i = 0; i < 8; ++i)
for (rint j = 0; j < 8; ++j) {
int nx = x + dx[i], ny = y + dy[i];
if (nx <= n && nx >= 1 && ny <= m && ny >= 1 && !v[nx][ny] && b[nx][ny]) {
v[nx][ny] = 1;
q.push_back(make_pair(nx, ny));
}
}
}
return 0;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("fluid.in", "r", stdin);
freopen("fluid.out", "w", stdout);
n = re(), m = re();
for (rint i = 1; i <= n; ++i) {
scanf("%s", s + 1);
for (rint j = 1; j <= m; ++j) {
if (s[j] == '1') {
a[i][j] = 1;
if (!spx)
spx = i, spy = j;
g.push_back(make_pair(i, j));
}
}
}
int nas = 1e9;
for (rint i = n - 1, j = 0; i >= 0; --i) {
while (j < m && !check(i, j)) ++j;
if (j == m || i >= nas)
break;
nas = min(nas, i + j);
}
chu("%d", nas);
return 0;
}
/*
5 4
1100
1000
0011
0000
0001
*/
正解100pts
上面的常数有点大(BFS?),所以换一个思路,考虑每个黑点向其他黑点连边,代价是\([x,y]\),就是坐标差-1,\(K^2\)条边会T,考虑如果存在\([x,y]->[x+d1,y],[x+d1,y]->[x+d1,y+d2],那么[x,y]->[x+d1,y+d2]就是完全没有必要的边\),所以直接只存对于每个\(black_{[x,y]}\)横坐标差值不同的纵坐标差值最小黑点(其实吧要求从上到下横坐标递减,但是不好操作),连边。对于生成树,设置第一维度x的上限,从小到大添加y直到联通,取min的答案。
注意一些剪枝,比如2维度边权<=0的就直接都放到树里,每次重新设置上界的时候对并查集只撤销已有联通块(注意把边赋值成father,否则路径压缩出问题)。
\(O(n^2logn)\)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register int
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 733;
int n, m, id[N][N], tim, f[N * N], cnt, fof, tot;
vector<int> H[N], L[N];
vector<int> dot;
struct edge {
int u, v, x, y;
} e[N * N * 10];
bool cmp(edge A, edge B) { return (A.x == B.x) ? (A.y < B.y) : (A.x < B.x); }
vector<edge> use[N];
char s[N][N];
inline int father(int x) { return (x == f[x]) ? x : (f[x] = father(f[x])); }
inline void merge(int x, int y) {
x = father(x), y = father(y);
if (x == y)
return;
f[x] = y;
--cnt;
}
int main() {
freopen("fluid.in", "r", stdin);
freopen("fluid.out", "w", stdout);
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n = re(), m = re();
for (rint i = 1; i <= n; ++i) {
scanf("%s", s[i] + 1);
for (rint j = 1; j <= m; ++j) {
if (s[i][j] == '1')
H[i].push_back(j), L[j].push_back(i);
id[i][j] = ++tot;
}
}
for (rint i = 1; i <= n; ++i) {
for (rint x : H[i]) {
for (rint j = x; j <= m; ++j) {
if (s[i][j] == '1' && j != x)
break;
int p = upper_bound(L[j].begin(), L[j].end(), i) - L[j].begin();
if (p >= L[j].size())
continue;
p = L[j][p];
e[++fof] = (edge){ id[i][x], id[p][j], p - i - 1, j - x - 1 };
}
for (rint j = x - 1; j >= 1; --j) {
if (s[i][j] == '1')
break;
int p = upper_bound(L[j].begin(), L[j].end(), i) - L[j].begin();
if (p >= L[j].size())
continue;
p = L[j][p];
e[++fof] = (edge){ id[i][x], id[p][j], p - i - 1, x - j - 1 };
}
}
for (rint j = 0; j + 1 < H[i].size(); ++j)
e[++fof] = (edge){ id[i][H[i][j]], id[i][H[i][j + 1]], -1, H[i][j + 1] - H[i][j] - 1 };
}
sort(e + 1, e + 1 + fof, cmp);
// for(rint i=1;i<=fof;++i)
// {
// chu("%d %d %d %d\n",e[i].u,e[i].v,e[i].x,e[i].y);
// }
for (rint i = 1; i <= tot; ++i) f[i] = i;
for (rint i = 1; i <= fof; ++i)
if (e[i].x <= 0 && e[i].y <= 0)
merge(e[i].u, e[i].v);
for (rint i = 1; i <= fof; ++i) {
e[i].u = father(e[i].u), e[i].v = father(e[i].v);
if (e[i].v == e[i].u)
continue;
use[e[i].y + 2].push_back(e[i]);
}
for (rint i = 1; i <= n; ++i)
for (rint j = 1; j <= m; ++j) {
if (s[i][j] == '1') {
int fat = father(id[i][j]);
if (fat == id[i][j])
dot.push_back(fat);
}
}
int eafoo = 1e9;
for (rint i = 0; i <= n; ++i) {
for (rint ele : dot) f[ele] = ele;
cnt = dot.size();
for (rint j = 1; j < m + 2; ++j) {
for (edge ele : use[j])
if (ele.x <= i)
merge(ele.u, ele.v);
else
break; // x递增的
if (cnt == 1) {
eafoo = min(eafoo, i + max(0, j - 2));
break;
}
if (i + max(0, j - 2) >= eafoo)
break;
}
}
chu("%d", eafoo);
return 0;
}
/*
5 4
1100
1000
0011
0000
0001
*/
[DP]T3:给出一棵树,有K个节点上有草莓,2个人同时从1出发一时刻移动1距离,去拿草莓回到1,求最少时间。(n<=415)
正解
只有叶子有草莓有用,有草莓的只有叶子有用,所以等价成把叶子集合分成2份使得max的覆盖路径和最小。
\(f[i][j][k]=t\)表示已经顺次找到i节点,路径和是k,不属于i集合的节点的最近一个是j,j属于的集合的最小价值。dfn序遍历一下就行。\(O(n^3)\)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register short int
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const short int N = 420; //但是我只想到了n^2的DP,是不是假了?假了?假了?(真的真的真的)
short int n, K, head[N], tot, fa[N], leaf[N], top[N], mson[N], dep[N], f[N][N][N], Leaf;
short int lca[N][N];
bool up[N];
struct node {
short int to, nxt;
} e[N << 1];
inline void add_e(short int u, short int v) {
e[++tot] = (node){ v, head[u] };
head[u] = tot;
}
inline void dfs(short int x, short int ff) {
for (rint i = head[x]; i; i = e[i].nxt) {
short int to = e[i].to;
if (to == ff)
continue;
dfs(to, x);
up[x] |= up[to];
}
}
inline void getleaf(short int x, short int ff) {
bool wson = 1;
fa[x] = ff;
for (rint i = head[x]; i; i = e[i].nxt) {
int to = e[i].to;
if (to == ff || up[to] == 0)
continue;
dep[to] = dep[x] + 1;
getleaf(to, x);
wson = 0;
}
if (wson)
leaf[++Leaf] = x; //叶子拿出来
}
inline int getlca(int x, int y) {
while (x != y) {
if (dep[x] > dep[y])
x = fa[x];
else
y = fa[y];
}
return x;
}
inline void upd(short int& x, short int y) { x = min(x, y); }
int main() {
freopen("gift.in", "r", stdin);
freopen("gift.out", "w", stdout);
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n = re();
K = re();
for (rint i = 1; i < n; ++i) {
short int u = re(), v = re();
add_e(u, v);
add_e(v, u);
}
for (rint i = 1; i <= K; ++i) {
short int x = re();
up[x] = 1;
}
dfs(1, 0); //向上传递必须遍历的标记(有草莓)
Leaf = 1;
leaf[1] = 1;
getleaf(1, 0);
for (rint i = 1; i < Leaf; ++i)
for (rint j = i + 1; j <= Leaf; ++j) lca[i][j] = lca[j][i] = getlca(leaf[i], leaf[j]);
for (rint i = 0; i <= n; ++i)
for (rint j = 0; j <= n; ++j)
for (rint k = 0; k <= n; ++k) f[i][j][k] = 32767;
// f[i][j][k]:前i个叶子,和i在一起的价值是k,和j在一起的价值是fijk
f[1][0][0] = 0;
dep[0] = 0;
leaf[0] = 0;
for (rint i = 1; i < Leaf; ++i) //不靠谱哇
{
// chu("think:%d\n",leaf[i]);
for (rint j = 0; j < i; ++j) // j可以没有吗?应该可以吧?都给i集合也是合法的决策
{
for (rint k = 0; k <= n; ++k) {
// chu("前%d个叶子,i集合价值:%d 上一个在另集合的是:%d 另集合价值:%d\n",i,k,j,f[i][j][k]);
if (k + dep[leaf[i + 1]] - dep[lca[i][i + 1]] <= n)
upd(f[i + 1][j][k + dep[leaf[i + 1]] - dep[lca[i][i + 1]]], f[i][j][k]);
if (f[i][j][k] + dep[leaf[i + 1]] - dep[lca[j][i + 1]] <= n)
upd(f[i + 1][i][f[i][j][k] + dep[leaf[i + 1]] - dep[lca[j][i + 1]]], k);
}
}
//如果j是0,代表没有j,就是i+1新创建集合?或者i+1和i合并,新集合继续没有
}
short int ar = 32767;
for (rint i = 0; i <= n; ++i)
for (rint j = 0; j < n; ++j) upd(ar, max(f[Leaf][i][j], j));
chu("%d", (int)ar * 2);
return 0;
}
/*
7 4
1 2
2 3
2 4
1 5
5 6
6 7
3 4 5 7
真的是想不出来了
觉得2个人可以看成一个,枚举中间的过1的断点
觉得可以把叶子拿出来(要是拿一定是拿到叶子),枚举二进制数,
O(2^lef * n)
*/