20240408
T1
Topcoder SRM 596 div1 Medium - BitwiseAnd
相当于每两个数都有公共位,每两个数按位与起来得到的数上任意的数位不能包含在另一个数中。考虑只加入一个数,相当于某些位不能填数,有若干组限制代表这组限制中有至少一位为 \(1\)。要求字典序最小,就选最小的这种数。从高位往低位贪心,当前位能不填就不填。如果到了某组限制中的最后一位而且这组限制还没有被满足,那当前位就实在是不得不填了。注意这样可能会满足另外一些限制。然后再加入下一个数,发现无法加入了,因为第一个填的数每一位初始的数都有,所以无法同时满足两个限制。所以就必须给填入的第一个数再加一位,使得这一位在所有数中都没有出现过。这样就能成功加入第二个数。再加入第三个数,发现又加不进去了,因为加入的第一个数的所有数位都包含在初始的数或第二个填入的数中,所以此时得再给第一个填入的数加一位。以此类推,可以发现填 \(m\) 个数到第 \(n\) 个时要多加 \(m - n\) 位之前没出现过的位。这样再结合前面说的贪心构造就可以了。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#define int long long
using namespace std;
int n, m;
int a[55];
bool no[65];
vector<int> req[65];
vector<int> lreq[65];
bool sat[65];
bool asdf[65];
void SetNo(int x) {
for (int i = 0; i < 60; i++) {
if ((x >> i) & 1)
no[i] = 1;
}
}
bool Insert(int cur, int x) {
for (int i = 1; i < cur; i++) {
SetNo(x & a[i]);
if ((x & a[i]) == 0)
return 0;
}
for (int i = 0; i < 60; i++) {
if ((x >> i) & 1)
asdf[i] = 1;
}
return 1;
}
int Generate(int n, int x) {
for (int i = 0; i < 60; i++) req[i].clear(), lreq[i].clear();
for (int i = 1; i <= n; i++) {
bool rec = 0;
for (int j = 0; j < 60; j++) {
if (((a[i] >> j) & 1) && !no[j]) {
req[j].emplace_back(i);
if (!rec)
lreq[j].emplace_back(i);
rec = 1;
}
}
}
memset(sat, 0, sizeof sat);
int ret = 0;
for (int i = 59; ~i; i--) {
if (no[i])
continue;
bool f = 0;
for (auto v : lreq[i]) {
if (!sat[v])
f = 1;
}
if (f) {
ret |= (1ll << i);
for (auto v : req[i])
sat[v] = 1;
}
}
for (int i = 0; i < 60; i++) {
if (!asdf[i] && x) {
ret |= (1ll << i);
--x;
}
}
return ret;
}
bool check() {
bool ret = 1;
for (int i = 1; i <= m; i++) {
for (int j = i + 1; j <= m; j++) {
ret &= ((a[i] & a[j]) != 0);
for (int k = j + 1; k <= m; k++)
ret &= ((a[i] & a[j] & a[k]) == 0);
}
}
return ret;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (!Insert(i, a[i])) {
cout << "\n";
return 0;
}
}
cin >> m;
for (int i = n + 1; i <= m; i++) {
int tmp = Generate(i, m - i);
if (tmp == -1) {
cout << "\n";
return 0;
}
if (!Insert(i, a[i] = tmp)) {
cout << "\n";
return 0;
}
}
sort(a + 1, a + m + 1);
if (!check()) {
cout << "\n";
return 0;
}
for (int i = 1; i <= m; i++) cout << a[i] << " \n"[i == m];
return 0;
}
T2
Topcoder SRM 587 div1 Medium - TriangleXor
考虑算同一行的所有四边形的面积和。注意到同一行四边形的中间两个顶点都是共线而且与坐标轴的,所以可以直接进行等积变形,这样就只需要知道底的长和上下的高。这两部分手列几个式子解一解就都能知道。然后考虑两边的三角形面积。这些东西也可以过其最靠中间的顶点作 \(x\) 轴的平行线来分割,然后底乘高算面积。这部分也是手列几个式子就能算。最终代码直接把手算出来的式子扔进去就可以了。我才不会告诉你我一开始推式子的时候所有三角形面积都没除以二,最后发现输出都是答案两倍的时候才反应过来。
代码
#include <iostream>
#include <iomanip>
#include <math.h>
using namespace std;
int w;
double ans;
int main() {
cin >> w;
for (int k = 1; k <= w; k += 2) {
ans += (w - (2.0 * w * k) / (w + k)) * ((k + 1.0) / (k + w + 1.0) - (k - 1.0) / (k + w - 1.0));
ans += 2.0 * w * w / ((k + w) * (k + w - 1.0));
}
if (!(w & 1))
ans += w / 2.0;
cout << floor(ans / 2) << "\n";
return 0;
}
T3
Topcoder SRM 560 div1 Medium - DrawingPointsDivOne
考虑二分答案,check 就把所有点扩出去再缩回来,看是否和原来的相等即可。是暴力,反正能过。
代码
#include <iostream>
#include <string.h>
using namespace std;
const int N = 600;
int n;
int x[55], y[55];
int clr[601][601];
int rec[601][601];
int cur = 1;
void expand() {
for (int i = 1 + (cur & 1); i < N; i += 2) {
for (int j = 1 + (cur & 1); j < N; j += 2) {
if (clr[i][j] == 1) {
clr[i + 1][j + 1] = 2;
clr[i + 1][j - 1] = 2;
clr[i - 1][j + 1] = 2;
clr[i - 1][j - 1] = 2;
}
}
}
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= N; j++) {
if (clr[i][j] == 1)
clr[i][j] = 0;
else if (clr[i][j] == 2)
clr[i][j] = 1;
}
}
++cur;
}
void shrink() {
for (int i = 1 + !(cur & 1); i < N; i += 2) {
for (int j = 1 + !(cur & 1); j < N; j += 2) {
if (clr[i + 1][j + 1] == 1 && clr[i + 1][j - 1] == 1
&& clr[i - 1][j + 1] == 1 && clr[i - 1][j - 1] == 1)
clr[i][j] = 2;
}
}
--cur;
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= N; j++) {
if (clr[i][j] == 1)
clr[i][j] = 0;
else if (clr[i][j] == 2)
clr[i][j] = 1;
}
}
}
bool chk(int k) {
memcpy(clr, rec, sizeof clr);
int tmp = k;
while (tmp--) expand();
while (k--) shrink();
bool ret = 1;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
ret &= (clr[i][j] == rec[i][j]);
}
return ret;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i];
x[i] += 71;
x[i] <<= 1;
x[i] += 140;
}
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> y[i];
y[i] += 71;
y[i] <<= 1;
y[i] += 140;
clr[x[i]][y[i]] = 1;
}
memcpy(rec, clr, sizeof clr);
int l = 0, r = 139, mid, ans = -1;
while (l <= r) {
mid = (l + r) >> 1;
if (chk(mid))
ans = mid, l = mid + 1;
else
r = mid - 1;
}
cout << (ans >= 139 ? -1 : ans) << "\n";
return 0;
}
T4
Topcoder SRM 591 div1 Medium - PyramidSequences
原问题相当于一张 \(n \times m\) 的网格图,从左上角往正右下方向发射一个弹球,弹球遇边界反弹,问你到最终结束时弹球走的路径覆盖了多少网格。首先 \(n\) 和 \(m\) 都减 \(1\),然后观察互质的情况,发现涂出来是黑白格间隔出现。然后考虑不互质的情况,先除掉 \(\gcd\),然后画出图(下称小图),再画出原图,观察发现实际上可以在原图上找到小图的影子:把小图中每两个对角相邻的点间插入一条长度为 \(\gcd - 1\) 的对角线之后可以构造出原图。这样就只需要求小图中有多少点和插入了多少点。这两部分都是好求的,手摸一下就看出来了。所以只需要求个 \(\gcd\) 然后即可。
代码
#include <iostream>
#define int long long
using namespace std;
int gcd(int a, int b) { return (b ? gcd(b, a % b) : a); }
signed main() {
int n, m, d;
cin >> n >> m;
d = gcd(n - 1, m - 1);
n = (n - 1) / d, m = (m - 1) / d;
cout << n * m * (d - 1) + (n + 1) * (m + 1) / 2 << "\n";
return 0;
}
T5
Topcoder SRM 578 div1 Hard - DeerInZooDivOne
考虑枚举先断开哪条边,这样就变成两个连通块,在这两个连通块里选点。再暴力枚举两个点作为根,设 \(f[a][b]\) 为当前根下两个连通块内以 \(a\) 为根的子树内和以 \(b\) 为根的子树内最大能选出来多少。考虑转移 \(f[u][v]\),先算出 \(son_u\) 和 \(son_v\) 两两之间的 \(f\),然后可以将 \(son_u\) 视为左部点,\(son_v\) 视为右部点,跑一个二分图最大权匹配,然后 \(f[u][v]\) 就是这个权 $ + 1$。非常暴力,而且复杂度也爆炸,但是反正能过。
代码
#include <iostream>
#include <queue>
#include <string.h>
using namespace std;
const int inf = 2147483647;
int n;
int a[55], b[55];
int f[55][55];
int id[55][55];
int head[55], nxt[105], to[105], ecnt = 1;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
class Flow {
int head[55], nxt[5005], to[5005], res[5005], ew[5005], ecnt;
int cur[55], cst, dist[55];
queue<int> q;
bool inq[55];
bool spfa() {
for (int i = 1; i <= T; i++) dist[i] = inf;
dist[S] = 0;
q.push(S);
while (!q.empty()) {
int x = q.front();
q.pop();
inq[x] = 0;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (dist[v] > dist[x] + ew[i] && res[i]) {
dist[v] = dist[x] + ew[i];
if (!inq[v]) {
inq[v] = 1;
q.push(v);
}
}
}
}
return (dist[T] != inf);
}
bool vis[55];
int dfs(int x, int flow) {
if (x == T)
return flow;
vis[x] = 1;
int ret = 0;
for (int i = cur[x]; i && flow; i = nxt[i]) {
cur[x] = i;
int v = to[i];
if (dist[v] == dist[x] + ew[i] && !vis[v] && res[i]) {
int tmp = dfs(v, min(flow, res[i]));
if (tmp) {
res[i] -= tmp;
res[i ^ 1] += tmp;
ret += tmp;
flow -= tmp;
cst += tmp * ew[i];
}
}
}
if (!ret)
dist[x] = inf;
vis[x] = 0;
return ret;
}
public:
void clear() {
ecnt = 1;
cst = 0;
memset(head, 0, sizeof head);
memset(cur, 0, sizeof cur);
memset(nxt, 0, sizeof nxt);
memset(to, 0, sizeof to);
memset(res, 0, sizeof res);
memset(ew, 0, sizeof ew);
memset(dist, 0, sizeof dist);
}
int S, T;
void add(int u, int v, int x, int y) {
to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = y, res[ecnt] = x;
to[++ecnt] = u, nxt[ecnt] = head[v], head[v] = ecnt, ew[ecnt] = -y, res[ecnt] = 0;
}
int dinic() {
while (spfa()) {
for (int i = 1; i <= T; i++) cur[i] = head[i];
dfs(S, inf);
}
return cst;
}
};
bool ban[55];
Flow tmp;
vector<int> s[2];
int ans = 0;
void dfs(int x, int fx, int y, int fy) {
if (f[x][y])
return;
id[x][0] = id[y][0] = 0;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fx && !ban[i >> 1])
id[x][++id[x][0]] = v;
}
for (int i = head[y]; i; i = nxt[i]) {
int v = to[i];
if (v != fy && !ban[i >> 1])
id[y][++id[y][0]] = v;
}
if (!id[x][0] || !id[y][0]) {
f[x][y] = 1;
return;
}
for (int i = 1; i <= id[x][0]; i++) {
for (int j = 1; j <= id[y][0]; j++)
dfs(id[x][i], x, id[y][j], y);
}
tmp.clear();
tmp.S = id[x][0] + id[y][0] + 1;
tmp.T = tmp.S + 1;
for (int i = 1; i <= id[x][0]; i++) tmp.add(tmp.S, i, 1, 0);
for (int i = 1; i <= id[y][0]; i++) tmp.add(i + id[x][0], tmp.T, 1, 0);
for (int i = 1; i <= id[x][0]; i++) {
for (int j = 1; j <= id[y][0]; j++)
tmp.add(i, id[x][0] + j, 1, -f[id[x][i]][id[y][j]]);
}
f[x][y] = 1 - tmp.dinic();
}
int fa[505];
void dfs(int x, int f, bool asdf) {
s[asdf].push_back(x);
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != f)
dfs(v, x, asdf);
}
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> b[i];
add(a[i] + 1, b[i] + 1);
add(b[i] + 1, a[i] + 1);
}
++n;
for (int i = 1; i < n; i++) {
s[0].clear(), s[1].clear();
int x = to[i << 1], y = to[i << 1 | 1];
dfs(x, y, 0);
dfs(y, x, 1);
ban[i] = 1;
for (auto a : s[0]) {
for (auto b : s[1]) {
memset(f, 0, sizeof f);
memset(id, 0, sizeof id);
dfs(a, 0, b, 0);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
ans = max(ans, f[i][j]);
}
}
}
ban[i] = 0;
}
cout << ans << "\n";
return 0;
}
大力猜结论,暴力出奇迹。
网络流做一般二分图最大权匹配时需要将左部点向汇连边,代表这个左部点可以不选,这样能够保证网络流不会为了保证最大流量而放弃最大费用。

浙公网安备 33010602011771号