《信息学奥赛一本通·高手专项训练》集训 Day 2
深度搜索
题目
我们有一个 行 列的矩阵 ,第 行第 列的数记作 。同时给定了一个长度为 的序列 。
定义一个长度为 的整数序列 是“好”,当且仅当其满足如下条件:
- 序列中每个数均满足 ;
- ,其中 为一种位运算(
&是与,|是或,^是异或)。
现在给定 和 以及 ,请你求出有多少满足条件的序列 。
两个序列不同,当且仅当它们的长度不同,或者存在一个位置不同。
这个答案可能很大,请你求出对 取模的结果。
题解
可以发现每一行的数都是捆绑在一起的,注意到 ,我们可以把每一行的数状态压缩为一个,这样就相当于从 个数中选任意个使他们经过 运算后的结果为 ,按编号从小到大深搜即可。
有个坑点是同样长度同样数字不同的位置也是不同的,所以一个组合对答案的贡献是他的全排列,预处理阶乘即可。
代码
#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;
}
题目
暑期一共 天,好心人小涛想给大家买汽水,小涛最多只能给大家花 元钱。由于每天汽水的价格都不固定,现在给出每天买汽水的花销,我们可以随意选择让小涛哪些天买汽水(当然总花费不能超过 )。请问最多一共能够花掉小涛多少钱呢?
题解
如果 很小,直接背包就可以了,但这题 很大, 很小,我们考虑直接搜索每天的汽水选或不选,暴力搜索是 的,我们可以折半搜索,再用双指针匹配两半的汽水即可,时间复杂度 。
代码
#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;
}
题目
魔法阵是一个 的格子(高 ,宽 ), 为偶数。佳佳手中有 个宝石(以 编号)。佳佳从最右上角的格子开始走,从一个格子可以走到上、下、左、右 个相邻的格子,但不能走出边界。每个格子必须且仅能到过 次,这样佳佳一共走了 个格子停止(随便停哪里)。佳佳每进入一个格子,就在该格子里放入一颗宝石。他是按顺序放的,也就是说——第 个进入的格子放入 号宝石。
如果两颗宝石的编号对 取模的值相同,则认为这两颗宝石相互之间有微妙的影响。也就是说,我们按照宝石的编号对 取模的值,将宝石分成 对,其中每对都恰有两颗宝石。对于每一对宝石,设第一颗宝石在第 行第 列,另一颗宝石在第 行第 列,那么定义这 个宝石的魔力影响值为 。
需要你求出的是,在所有合乎题意的宝石摆放方案中,所有成对的宝石间的最大魔力影响值的最小值为多少。换句话说,如果我们定义对 取模的值为 的一对宝石的魔力影响值为 。你需要求出的就是 的最小值。
题解
起点为 ,由于 不超过 ,我们可以直接搜索路径,求出每种路径的最大值并取最小值。
但是还是可以剪枝的:
- 可行性剪枝:若要走的这个点的上下都被走过而左右没有,或左右都被走过而上下没有,则这个点不能选。以前者为例,若上下都被走过而左右没有,因为是同一条路,所以上下必然有另一条路联通,而左右没有说明这个点将网格分成了两个空闲网格,不符合要求。后者亦是如此。
- 最优性剪枝:若当前得出来的最大值已经不小于已算过的路径的最小值,则对答案没有改进的机会,不必搜索下去了。
代码
#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;
}
广度搜索
题目
在比特镇一共有 个街区,编号依次为 到 ,它们之间通过若干条单向道路连接。
比特镇的交通系统极具特色,除了 条单向道路之外,每个街区还有一个编码 ,不同街区可能拥有相同的编码。如果 ,即 在二进制下与 做与运算等于 ,那么也会存在一条额外的从 出发到 的单向道路。
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;
}
题目
Z 国有 个城市,标号为 。
城市之间的交通可以通过道路或航线,其中道路是双向的,但是航线是单向的。
最初始的时候这些城市之间没有道路和航线相连,然后 Z 国的基建部门依次修建了 条道路或航线。
小明与小红是好朋友,小明家住编号为 的城市,小红家住编号为 的城市。
小明想知道,在基建部门建设的第几条道路或航线后从 城可以到 城。
小红想知道,在基建部门建设的第几条道路或航线后从 城可以到 城。 帮帮他们吧。
保证修建完 条道路或航线后既可以从 到 ,也可以从 到 。
题解
由于 高达 ,需要 的算法,所以我们只能一条边一条边地加,实时更新到达的情况。
以 到 为例求解,把当前所有 可以到达的点打上标记,对于新加入的边,如果是道路我们就把它拆成两条航线来做。
设加入的单向边为 :
- 若 都能被 访问到,则这条边没用处。
- 若 能, 不能,则这条边能联通 ,从 开始再 ,标记能访问到的点。
- 若 都不能,则这条边可能在下次的上一个情况中起作用,把它加入到图中。
不断加边,直到 被标记到即可。
代码
#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;
}
题目
小明和小方玩一个挑卡片游戏。
小明会给出 对信封,每个信封里有两张不同颜色的卡片,他会让小方从中挑选任意个信封,但是一对信封中最多只能挑选一个,(信封是透明的,可以看到里面卡片颜色)。等小方挑好后,小明会尝试从小方挑出的信封中再选出若干个(不可以不取),把其中的卡片取出,若存在一种方案使得取出的卡片中,每种颜色的卡片都有偶数张,那么小明就赢了。小方想知道在自己赢的前提下,他最多能选出多少信封。
题解
颜色数很大,而信封很少,所以可以先将颜色离散化。
如果直接看题目,很难想出解法,但如果把卡片看成点,信封看成边,题目就变成了选最多的边使这些边构成的图中没有环。
枚举每对边尝试加入,能加哪个就加哪个,如果两边都加不了,就改变之前的选择,仿照二分图匹配寻找增广路(未选边 已选边 未选边 未选边),将所有边的选择情况取反。
增广其实也可以理解为边的替换关系,选了一条未选边,就得替换一条已选边,已选边又可能和另一条未选边有替换关系,以此类推,直到找到一条两端不连通的未选边(选了这条边后不形成环)。所以增广的连边有两种情况:
- 在一对信封(边)中相互连边,显然不选这封就可选那封。
- 对于一条未选择的边,如果他两端连通(选了这条边后形成环),在他与环上的其它点连边,因为选了这条边后环上其他边必须去掉一条。
循环 次一次加入所有对信封后把所有可选边计数得到答案。
代码
#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;
}

浙公网安备 33010602011771号