2018冬令营模拟测试赛(十)
2018冬令营模拟测试赛(十)
[Problem A]杨柳
试题描述
输入
见“试题描述”
输出
见“试题描述”
输入示例
见“试题描述”
输出示例
见“试题描述”
数据规模及约定
见“试题描述”
题解
直接在网格上按题意建图,跑最小费用最大流就好了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 10010
#define maxm 162010
#define oo 2147483647
struct Edge {
int from, to, flow, cost;
Edge() {}
Edge(int _1, int _2, int _3, int _4): from(_1), to(_2), flow(_3), cost(_4) {}
};
struct ZKW {
int n, m, s, t, ans, cost, head[maxn], nxt[maxm];
Edge es[maxm];
bool inq[maxn];
int d[maxn], Q[maxn], hd, tl;
bool vis[maxn];
void init() {
ans = m = 0; memset(head, -1, sizeof(head));
return ;
}
void setn(int _) {
n = _;
return ;
}
void AddEdge(int a, int b, int c, int d) {
es[m] = Edge(a, b, c, d); nxt[m] = head[a]; head[a] = m++;
es[m] = Edge(b, a, 0, -d); nxt[m] = head[b]; head[b] = m++;
return ;
}
int Nxt(int u) { return (u + 1) % maxn; }
bool BFS() {
rep(i, 1, n) d[i] = oo;
d[t] = 0;
hd = tl = 0; Q[tl = Nxt(tl)] = t; inq[t] = 1;
while(hd != tl) {
int u = Q[hd = Nxt(hd)]; inq[u] = 0;
for(int i = head[u]; i != -1; i = nxt[i]) {
Edge& e = es[i^1];
if(e.flow && d[e.from] > d[u] + e.cost) {
d[e.from] = d[u] + e.cost;
if(!inq[e.from]) inq[e.from] = 1, Q[tl = Nxt(tl)] = e.from;
}
}
}
if(d[s] == oo) return 0;
cost = d[s];
return 1;
}
int DFS(int u, int a) {
if(u == t || !a) return ans += cost * a, a;
if(vis[u]) return 0;
vis[u] = 1;
int flow = 0, f;
for(int i = head[u]; i != -1; i = nxt[i]) {
Edge& e = es[i];
if(d[e.to] == d[u] - e.cost && (f = DFS(e.to, min(a, e.flow)))) {
flow += f; a -= f;
e.flow -= f; es[i^1].flow += f;
if(!a) return flow;
}
}
return flow;
}
int MaxFlow(int _s, int _t) {
s = _s; t = _t;
int flow = 0, f;
while(BFS())
do {
memset(vis, 0, sizeof(vis));
f = DFS(s, oo);
flow += f;
} while(f);
return flow;
}
} sol;
#define maxr 110
char Map[maxr][maxr];
int CntP;
struct Point {
int id;
Point(): id(0) {}
int p() { return id ? id : id = ++CntP; }
} grid[maxr][maxr], S, T;
int main() {
int r = read(), c = read(), n = read(), a = read(), b = read();
rep(i, 1, r) scanf("%s", Map[i] + 1);
sol.init();
rep(i, 1, n) {
int x = read(), y = read();
sol.AddEdge(S.p(), grid[x][y].p(), 1, 0);
}
rep(i, 1, n) {
int x = read(), y = read();
sol.AddEdge(grid[x][y].p(), T.p(), 1, 0);
}
int dx[] = {a, a, -a, -a, b, b, -b, -b}, dy[] = {b, -b, b, -b, a, -a, a, -a};
rep(i, 1, r) rep(j, 1, c) if(Map[i][j] == '.') {
rep(d, 0, 7) {
int x = i + dx[d], y = j + dy[d];
if(1 <= x && x <= r && 1 <= y && y <= c && Map[x][y] == '.') sol.AddEdge(grid[i][j].p(), grid[x][y].p(), oo, 1);
}
}
sol.setn(CntP);
int f = sol.MaxFlow(S.p(), T.p());
if(f < n) return puts("-1"), 0;
printf("%d\n", sol.ans);
return 0;
}
[Problem B]景中人
试题描述
输入
见“试题描述”
输出
见“试题描述”
输入示例
见“试题描述”
输出示例
见“试题描述”
数据规模及约定
见“试题描述”
题解
直接 \(O(n^4)\) 区间 dp 即可。
先 \(x, y\) 坐标分别离散化,令 \(f(l, r, h)\) 表示对于 \(x\) 坐标在 \([l, r]\) 中的点,当前矩形覆盖到了高度 \(h\),这种情况下需要最少的矩形数。转移就是要么什么都不做,直接分左右两边转移;要么覆盖上一个最高的矩形(即下一次转移到满足 \((r - l) \cdot H\) 的最大的 \(H\)),然后往下转移。
用记忆化搜索,很多状态都不会被搜到,所以 \(O(Tn^4)\) 能过。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 110
#define oo 2147483647
#define LL long long
int n, S, x[maxn], y[maxn], mxh[maxn], num[maxn], numy[maxn], cn, cny;
int f[maxn][maxn][maxn];
int dp(int l, int r, int h) {
int &ans = f[l][r][h];
if(ans >= 0) return ans;
int mh = 0;
rep(i, l, r) mh = max(mh, mxh[i]);
if(mh <= h) return ans = 0;
if(l == r) return ans = 1;
ans = oo;
// do nothing
rep(i, l, r - 1) ans = min(ans, dp(l, i, h) + dp(i + 1, r, h));
// use 1 rectangle
int nxth = -1;
dwn(i, cny, h) if((LL)(num[r] - num[l]) * numy[i] <= S){ nxth = i; break; }
rep(i, l, r - 1) ans = min(ans, dp(l, i, nxth) + 1 + dp(i + 1, r, nxth));
return ans;
}
void work() {
n = read(); S = read();
rep(i, 1, n) num[i] = x[i] = read(), numy[i] = y[i] = read();
sort(num + 1, num + n + 1); sort(numy + 1, numy + n + 1);
cn = unique(num + 1, num + n + 1) - num - 1; cny = unique(numy + 1, numy + n + 1) - numy - 1;
memset(mxh, 0, sizeof(mxh));
rep(i, 1, n)
x[i] = lower_bound(num + 1, num + cn + 1, x[i]) - num,
y[i] = lower_bound(numy + 1, numy + cny + 1, y[i]) - numy,
mxh[x[i]] = max(mxh[x[i]], y[i]);
memset(f, -1, sizeof(f));
printf("%d\n", dp(1, cn, 0));
return ;
}
int main() {
int T = read();
while(T--) work();
return 0;
}
[Problem C]钦点
试题描述
输入
见“试题描述”
输出
见“试题描述”
输入示例
见“试题描述”
输出示例
见“试题描述”
数据规模及约定
见“试题描述”
题解
容易想象会有一些点之间会连成一个几乎是完全图的图;比如有 \(k\) 个数都是 \(20\) 的倍数,那么这 \(k\) 个点一定会组成一个完全图,但是题目只要求连通性,所以我们可以改造一下这个图的构造方式使得它的连通性保留即可。
首先一个暴力的思路就是将一个权值为 \(v\) 的点连向所有 \(v\) 的合数约数所代表的点,这样如果原图中两个点有一条边,新图中一定会在那对应两个点之间有一条三个节点的路径。
但是 \(10^7\) 内的数最多可能有 \(448\) 个约数,时间暂且不考虑,空间已经炸了(读者不妨自己算一下)。
其实我们并不需要连接每个约数,我们只需要考虑 \(v\) 的质因子就好了,由于它要求是合数,合数至少需要两个质因子,于是把 \(v\) 的所有不同质因子拿出来,然后两两乘积所代表的点和权值为 \(v\) 的点连起来就好了。
然后它要问拿掉一个点后最大的连通块最小是多大。我的做法就是跑一个点双的 tarjan,然后建出那个黑白染色的森林(黑点是割顶、白点是一个双连通分量缩成的点),搞出最大、次大的树,然后在最大的树里面枚举一下删每个割顶中能得到的最小的最大连通块,然后和次大的树大小取一个 \(\mathrm{max}\)。
注意统计连通块大小时只有原图中的点是有重量的,删掉的割顶也必须是原图中的点。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxv 10000001
#define maxn 100010
#define maxnode 2900010
#define maxm 5600010
#define LL long long
#define pii pair <int, int>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
bool tag[maxv];
int prime[maxv], cp;
void init(int n) {
rep(i, 2, n) {
if(!tag[i]) prime[++cp] = i;
for(int j = 1; j <= cp && i * prime[j] <= n; j++) {
tag[i*prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
return ;
}
struct Graph {
int n, m, head[maxnode], nxt[maxm], to[maxm];
void init() {
m = 0; memset(head, 0, sizeof(head));
return ;
}
void AddEdge(int a, int b) {
// printf("edge: %d %d\n", a, b);
to[++m] = b; nxt[m] = head[a]; head[a] = m;
swap(a, b);
to[++m] = b; nxt[m] = head[a]; head[a] = m;
// if(m % 100000 == 0) printf("m: %d\n", m);
return ;
}
} G, tr;
int n, clo, low[maxnode], dfn[maxnode], top, cntb, bcno[maxnode], siz[maxnode];
pii S[maxnode];
bool iscut[maxnode];
vector <int> cutout[maxnode];
void dfs(int u, int fa) {
low[u] = dfn[u] = ++clo;
for(int e = G.head[u]; e; e = G.nxt[e]) if(G.to[e] != fa) {
if(dfn[G.to[e]]) low[u] = min(low[u], dfn[G.to[e]]);
else {
S[++top] = mp(u, G.to[e]);
dfs(G.to[e], u);
low[u] = min(low[u], low[G.to[e]]);
if(low[G.to[e]] >= dfn[u]) {
iscut[u] = 1; cntb++;
while(1) {
pii e = S[top--];
/*if(bcno[e.x] != cntb) printf("%d: %d\n", cntb, e.x);
if(bcno[e.y] != cntb) printf("%d: %d\n", cntb, e.y); // */
bcno[e.x] = bcno[e.y] = cntb;
if(iscut[e.x]) cutout[e.x].push_back(cntb);
if(iscut[e.y]) cutout[e.y].push_back(cntb);
if(e.x == u || e.y == u) break;
}
}
}
}
return ;
}
int trsiz[maxnode], f[maxnode];
bool vis[maxnode], cutter[maxnode];
void dp(int u, int fa) {
vis[u] = 1;
for(int e = tr.head[u]; e; e = tr.nxt[e]) if(!vis[tr.to[e]]) dp(tr.to[e], u), trsiz[u] += trsiz[tr.to[e]];
return ;
}
void dp2(int u, int fa, int allsiz) {
vis[u] = 1;
int ssiz = 0;
for(int e = tr.head[u]; e; e = tr.nxt[e]) if(!vis[tr.to[e]]) {
dp2(tr.to[e], u, allsiz);
if(trsiz[tr.to[e]]) f[u] = max(f[u], trsiz[tr.to[e]]);
ssiz += trsiz[tr.to[e]];
}
if(allsiz - trsiz[u]) f[u] = max(f[u], allsiz - trsiz[u]);
if(!cutter[u]) f[u] = -1;
// else printf("dp2 %d: %d\n", u, f[u]);
return ;
}
int CntP;
struct Point {
int id;
Point(): id(0) {}
int p() { return id ? id : id = ++CntP; }
} vs[maxv], ns[maxnode];
int tmp[maxn];
void work() {
n = read();
// printf("n: %d\n", n);
CntP = n; G.init(); tr.init();
memset(vs, 0, sizeof(vs));
rep(i, 1, n) {
int v = read(), m = (int)sqrt(v + .5), cnt = 0, tv = v;
for(int j = 1; prime[j] <= m; j++) if(tv % prime[j] == 0) {
tmp[++cnt] = prime[j];
while(tv % prime[j] == 0) tv /= prime[j];
}
if(tv > 1) tmp[++cnt] = tv;
rep(j, 1, cnt) rep(k, j, cnt) if(v % ((LL)tmp[j] * tmp[k]) == 0)
G.AddEdge(vs[tmp[j]*tmp[k]].p(), i);
}
G.n = CntP;
// printf("CntP: %d\n", CntP);
clo = cntb = 0;
rep(i, 1, G.n) low[i] = dfn[i] = bcno[i] = siz[i] = vs[i].id = ns[i].id = trsiz[i] = iscut[i] = cutter[i] = 0;
rep(i, 1, G.n) cutout[i].clear();
rep(i, 1, G.n) if(!dfn[i]) dfs(i, 0);
// puts("-----");
CntP = 0;
rep(i, 1, G.n) if(!iscut[i] && i <= n) siz[bcno[i]]++;
rep(i, 1, G.n) if(iscut[i]) {
rep(j, 0, (int)cutout[i].size() - 1) tr.AddEdge(ns[i].p(), vs[cutout[i][j]].p());
if(i <= n) trsiz[ns[i].p()] = cutter[ns[i].p()] = 1;
}
rep(i, 1, cntb) trsiz[vs[i].p()] = siz[i];
tr.n = CntP;
int sum = 0;
rep(i, 1, tr.n) sum += trsiz[i];
// printf("tot: %d\n", sum);
int mxi, mx = -1, mx2 = -1;
rep(i, 1, tr.n) vis[i] = 0;
rep(i, 1, tr.n) if(!vis[i]) {
// printf("root: %d\n", i);
dp(i, 0);
if(mx <= trsiz[i]) mx2 = mx, mx = trsiz[i], mxi = i;
else if(mx2 < trsiz[i]) mx2 = trsiz[i];
}
// printf("mx2: %d\n", mx2);
rep(i, 1, tr.n) f[i] = -1, vis[i] = 0;
dp2(mxi, 0, mx);
int mn = n + 1;
rep(i, 1, tr.n) if(f[i] >= 0) mn = min(mn, f[i]);
printf("%d\n", max(mx2, mn <= n ? mn : 0));
return ;
}
int main() {
int T = read();
init(maxv - 1);
while(T--) work();
return 0;
}