《信息学奥赛一本通·高手专项训练》集训 Day 2
luckydrawbox · · 个人记录
深度搜索
\color{#FFC116}\text{A.入门数学}
题目
我们有一个
定义一个长度为
- 序列中每个数均满足
1\le p_i\le n) ; -
i\neq j\leftrightarrow p_i\neq p_j -
现在给定
两个序列不同,当且仅当它们的长度不同,或者存在一个位置不同。
这个答案可能很大,请你求出对
题解
可以发现每一行的数都是捆绑在一起的,注意到
有个坑点是同样长度同样数字不同的位置也是不同的,所以一个组合对答案的贡献是他的全排列,预处理阶乘即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 30;
const ll mod = 1e9 + 9;
int n, m, a[N][N], x[N], v[N], cnt[N], p[N];
vector<int> e;
int wan;
ll ans, jc[N];
char c;
void dfs1(int s, int now, int res) {
if (now > n) {
if (res == wan && s)
ans = (ans + jc[s]) % mod;
return;
}
dfs1(s, now + 1, res);
dfs1(s + 1, now + 1, res & p[now]);
}
void dfs2(int s, int now, int res) {
if (now > n) {
if (res == wan && s)
ans = (ans + jc[s]) % mod;
return;
}
dfs2(s, now + 1, res);
dfs2(s + 1, now + 1, res | p[now]);
}
void dfs3(int s, int now, int res) {
if (now > n) {
if (res == wan && s)
ans = (ans + jc[s]) % mod;
return;
}
dfs3(s, now + 1, res);
dfs3(s + 1, now + 1, res ^ p[now]);
}
int main() {
freopen("xx.in", "r", stdin);
freopen("xx.out", "w", stdout);
cin >> c;
jc[1] = 1;
for (int i = 2; i <= 26; i++) jc[i] = jc[i - 1] * i % mod;
n = read();
m = read();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j] = read();
}
}
for (int i = 1; i <= m; i++) {
x[i] = read();
}
if (c == '&') {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
p[i] |= (a[i][j] << (j - 1));
}
}
for (int i = 1; i <= m; i++) wan |= (x[i] << (i - 1));
dfs1(0, 1, (1 << m) - 1);
cout << ans << endl;
} else if (c == '|') {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
p[i] |= (a[i][j] << (j - 1));
}
}
for (int i = 1; i <= m; i++) wan |= (x[i] << (i - 1));
dfs2(0, 1, 0);
cout << ans << endl;
} else if (c == '^') {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
p[i] |= (a[i][j] << (j - 1));
}
}
for (int i = 1; i <= m; i++) {
wan |= (x[i] << (i - 1));
}
dfs3(0, 1, 0);
cout << ans << endl;
}
return 0;
}
\color{#FFC116}\text{B. 购买汽水}
题目
暑期一共
题解
如果
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 50;
int n;
vector<ll> e[2];
ll m, p[N], ans;
void dfs(int t, int now, int limit, ll mx) {
if (now > limit) {
e[t].push_back(mx);
return;
}
dfs(t, now + 1, limit, mx);
if (mx + p[now] <= m)
dfs(t, now + 1, limit, mx + p[now]);
}
int main() {
freopen("drink.in", "r", stdin);
freopen("drink.out", "w", stdout);
n = read();
m = read();
for (int i = 1; i <= n; i++) {
p[i] = read();
}
if (n < 2) {
if (p[1] <= m)
cout << p[1] << endl;
else
cout << 0 << endl;
return 0;
}
dfs(0, 1, n / 2, 0);
dfs(1, n / 2 + 1, n, 0);
sort(e[0].begin(), e[0].end());
sort(e[1].begin(), e[1].end());
for (int i = 0, j = e[1].size() - 1; i < e[0].size(); i++) {
while (j >= 0 && e[0][i] + e[1][j] > m) {
j--;
}
if (j >= 0 && e[0][i] + e[1][j] <= m) {
ans = max(ans, e[0][i] + e[1][j]);
}
}
write(ans);
return 0;
}
\color{#3498DB}\text{C. 宝石布阵}
题目
魔法阵是一个
如果两颗宝石的编号对
需要你求出的是,在所有合乎题意的宝石摆放方案中,所有成对的宝石间的最大魔力影响值的最小值为多少。换句话说,如果我们定义对
题解
起点为
但是还是可以剪枝的:
- 可行性剪枝:若要走的这个点的上下都被走过而左右没有,或左右都被走过而上下没有,则这个点不能选。以前者为例,若上下都被走过而左右没有,因为是同一条路,所以上下必然有另一条路联通,而左右没有说明这个点将网格分成了两个空闲网格,不符合要求。后者亦是如此。
- 最优性剪枝:若当前得出来的最大值已经不小于已算过的路径的最小值,则对答案没有改进的机会,不必搜索下去了。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 60;
int n, m, k1, k2, ans = 1e9;
int fx[4] = { 0, 0, 1, -1 };
int fy[4] = { 1, -1, 0, 0 };
int v[N][N], a[N * N][5], tot[N * N], b[N][N];
bool check(int x, int y) {
if (x > 0 && x <= n && y > 0 && y <= m && !v[x][y])
return 1;
return 0;
}
void dfs(int x, int y, int t, int mx) {
if (mx >= ans)
return;
if (t == n * m + 1) {
int mz = 0;
for (int i = 0; i < n * m / 2; i++) {
mz = max(mz, k1 * abs(a[i][1] - a[i][3]) + k2 * abs(a[i][2] - a[i][4]));
}
ans = min(ans, mz);
return;
}
for (int i = 0; i < 4; i++) {
int xx = x + fx[i], yy = y + fy[i];
if (!check(xx, yy))
continue;
int my = mx;
if (check(xx + 1, yy) && check(xx - 1, yy) && !check(xx, yy - 1) && !check(xx, yy + 1))
continue;
if (!check(xx + 1, yy) && !check(xx - 1, yy) && check(xx, yy - 1) && check(xx, yy + 1))
continue;
if (tot[t % (n * m / 2)] == 2)
my = max(my, k1 * abs(a[t % (n * m / 2)][1] - xx) + k2 * abs(a[t % (n * m / 2)][2] - yy));
v[xx][yy] = 1;
a[t % (n * m / 2)][++tot[t % (n * m / 2)]] = xx;
a[t % (n * m / 2)][++tot[t % (n * m / 2)]] = yy;
dfs(xx, yy, t + 1, my);
v[xx][yy] = 0;
tot[t % (n * m / 2)]--;
tot[t % (n * m / 2)]--;
}
}
int main() {
freopen("mmatrix.in", "r", stdin);
freopen("mmatrix.out", "w", stdout);
n = read();
m = read();
k1 = read();
k2 = read();
v[1][m] = 1;
a[1][1] = 1;
a[1][2] = m;
tot[1] = 2;
dfs(1, m, 2, 0);
cout << ans << endl;
return 0;
}
广度搜索
\color{#52C41A}\text{A. 游走距离}
题目
在比特镇一共有
比特镇的交通系统极具特色,除了
Byteasar 现在位于
题解
因为
可以分析得出:如果有一条额外边
把所有的点
由于边权有两种,把
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 3e5 + 10, M = 1e6 + 10, S = 12;
int n, m, val[N], len[N + M];
int head[N + M], ver[N + M * S], nxt[N + M * S], edge[N + M * S], tot;
priority_queue<pair<int, int> > q;
void add(int x, int y, int z) {
ver[++tot] = y;
edge[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
void bfs() {
q.push(make_pair(0, 1));
for (int i = 1; i <= n + (1 << 20); i++) len[i] = -1;
len[1] = 0;
while (q.size()) {
int x = q.top().second;
q.pop();
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (len[y] >= 0)
continue;
len[y] = len[x] + edge[i];
q.push(make_pair(-len[y], y));
}
}
}
int main() {
n = read();
m = read();
for (int i = 1; i <= n; i++) {
val[i] = read();
add(i, n + val[i] + 1, 1);
add(n + val[i] + 1, i, 0);
}
for (int i = 1; i <= m; i++) {
int u, v;
u = read();
v = read();
add(u, v, 1);
}
for (int i = 0; i < (1 << 20); i++)
for (int j = 0; j < 20; j++)
if ((i >> j) & 1)
add(n + i + 1, n + i - (1 << j) + 1, 0);
bfs();
for (int i = 1; i <= n; i++)
cout << len[i] << endl;
return 0;
}
\color{#52C41A}\text{B. 道路航线}
题目
Z 国有
城市之间的交通可以通过道路或航线,其中道路是双向的,但是航线是单向的。
最初始的时候这些城市之间没有道路和航线相连,然后 Z 国的基建部门依次修建了
小明与小红是好朋友,小明家住编号为
小明想知道,在基建部门建设的第几条道路或航线后从
小红想知道,在基建部门建设的第几条道路或航线后从
保证修建完
题解
由于
以
设加入的单向边为
- 若
u,v 都能被a 访问到,则这条边没用处。 - 若
u 能,v 不能,则这条边能联通u,v ,从v 开始再\text{bfs} ,标记能访问到的点。 - 若
u,v 都不能,则这条边可能在下次的上一个情况中起作用,把它加入到图中。
不断加边,直到
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 3e6 + 10;
int n, m, a, b, bi[N][3];
int head[N], ver[N << 1], nxt[N << 1], tot;
bool v[N];
void add(int x, int y) {
ver[++tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
bool check(int s, int t, int ed) {
if (v[s] && v[t])
return 0;
if (v[s] && !v[t]) {
v[t] = 1;
queue<int> q;
q.push(t);
while (q.size()) {
int x = q.front();
q.pop();
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (!v[y]) {
v[y] = 1;
q.push(y);
}
}
head[x] = 0;
}
}
if (!v[s])
add(s, t);
return v[ed];
}
int solve(int s, int t) {
v[s] = 1;
for (int i = 1; i <= m; i++) {
if (check(bi[i][0], bi[i][1], t))
return i;
if (bi[i][2] == 0) {
if (check(bi[i][1], bi[i][0], t))
return i;
}
}
return m;
}
int main() {
n = read();
m = read();
a = read();
b = read();
for (int i = 1; i <= m; i++) {
bi[i][0] = read();
bi[i][1] = read();
bi[i][2] = read();
}
cout << solve(a, b) << " ";
tot = 0;
memset(head, 0, sizeof(head));
memset(ver, 0, sizeof(ver));
memset(nxt, 0, sizeof(nxt));
memset(v, 0, sizeof(v));
cout << solve(b, a) << " ";
return 0;
}
\color{#3498DB}\text{C. 选择信封}
题目
小明和小方玩一个挑卡片游戏。
小明会给出
题解
颜色数很大,而信封很少,所以可以先将颜色离散化。
如果直接看题目,很难想出解法,但如果把卡片看成点,信封看成边,题目就变成了选最多的边使这些边构成的图中没有环。
枚举每对边尝试加入,能加哪个就加哪个,如果两边都加不了,就改变之前的选择,仿照二分图匹配寻找增广路(未选边
增广其实也可以理解为边的替换关系,选了一条未选边,就得替换一条已选边,已选边又可能和另一条未选边有替换关系,以此类推,直到找到一条两端不连通的未选边(选了这条边后不形成环)。所以增广的连边有两种情况:
- 在一对信封(边)中相互连边,显然不选这封就可选那封。
- 对于一条未选择的边,如果他两端连通(选了这条边后形成环),在他与环上的其它点连边,因为选了这条边后环上其他边必须去掉一条。
循环
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 610;
int n, m, fa[N << 2], b[N << 2], from[N << 1], visp[N << 2], ans;
bool ok[N << 1], v[N << 1], ring[N << 1];
struct asdf {
int u, v, use;
} e[N << 1];
struct ep {
int v, p;
};
vector<ep> head[N << 2];
vector<int> ti[N << 1];
queue<int> q;
int get(int x) { return (x == fa[x] ? x : fa[x] = get(fa[x])); }
bool can_insert(int x) {
int u = get(e[x].u), v = get(e[x].v);
if (u == v)
return 0;
fa[u] = v;
e[x].use = 1;
return 1;
}
bool is_ring(int now, int t, int sp) {
if (now == t)
return 1;
visp[now] = sp;
for (int i = 0; i < head[now].size(); i++) {
int y = head[now][i].v;
if (visp[y] == sp)
continue;
ring[head[now][i].p] = 1;
if (is_ring(y, t, sp))
return 1;
ring[head[now][i].p] = 0;
}
return 0;
}
void build(int x) {
memset(ok, 0, sizeof(ok));
memset(visp, -1, sizeof(visp));
for (int i = 0; i <= x; i++) ti[i].push_back(i ^ 1);
for (int i = 0; i <= x; i++)
if (!e[i].use) {
ok[i] = 1;
memset(ring, 0, sizeof(ring));
if (!is_ring(e[i].u, e[i].v, i))
continue;
ok[i] = 0;
for (int j = 0; j < x; j++)
if (ring[j])
ti[i].push_back(j);
}
}
bool bfs(int x) {
memset(v, 0, sizeof(v));
memset(from, -1, sizeof(from));
int end = -1;
q.push(x);
v[x] = 1;
while (q.size()) {
int p = q.front();
q.pop();
for (int i = 0; i < ti[p].size(); i++) {
int y = ti[p][i];
if (v[y])
continue;
v[y] = 1;
if (e[p].use ^ e[y].use) {
from[y] = p;
if (ok[y]) {
while (q.size()) q.pop();
end = y;
break;
}
q.push(y);
}
}
}
if (end == -1)
return 0;
for (; end != -1; end = from[end]) e[end].use ^= 1;
return 1;
}
void init(int t) {
for (int i = 0; i <= m; i++) fa[i] = i;
for (int i = 0; i < 4 * n; i++) head[i].clear();
for (int i = 0; i < 2 * n; i++) ti[i].clear();
for (int i = 0; i < 2 * t; i++) {
if (e[i].use) {
fa[get(e[i].u)] = get(e[i].v);
head[e[i].u].push_back((ep){ e[i].v, i });
head[e[i].v].push_back((ep){ e[i].u, i });
}
}
}
int main() {
n = read();
for (int i = 0; i < 2 * n; i++) {
e[i].u = read();
e[i].v = read();
b[i * 2 + 1] = e[i].u;
b[i * 2 + 1 + 1] = e[i].v;
}
sort(b + 1, b + 4 * n + 1);
m = unique(b + 1, b + 4 * n + 1) - b - 1;
for (int i = 0; i < 2 * n; i++) {
e[i].u = lower_bound(b + 1, b + m + 1, e[i].u) - b;
e[i].v = lower_bound(b + 1, b + m + 1, e[i].v) - b;
}
for (int i = 0; i < n; i++) {
init(i);
if (can_insert(i * 2))
continue;
if (can_insert(i * 2 + 1))
continue;
build(i * 2 + 1);
if (bfs(i * 2))
continue;
bfs(i * 2 + 1);
}
for (int i = 0; i < 2 * n; i++) ans += e[i].use;
write(ans);
return 0;
}