官方数据和标程:2022杭电多校资料包
目录
难度评价
5题快一百名,4题快两百名,3题慢四百名。补到6、7题还是很合理的,金牌补到9题以上。
B、K、L签到,A、C、H、I中等,D困难,E、F、G、J大难
A. String
字符串
待补
B. Dragon Slayer
暴力
、搜索
墙数量只有15,直接暴力枚举所有情况再搜索。坐标处理上可以扩大2倍,但会慢一些,容易被卡常,可以在枚举墙时剪枝。
#include <bits/stdc++.h>
using namespace std;
const int mx[] = {-1, 0, 1, 0}, my[] = {0, 1, 0, -1};
bool ban[33][33], vis[33][33];
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, m, k, sx, sy, dx, dy;
cin >> n >> m >> k, n *= 2, m *= 2;
cin >> sx >> sy >> dx >> dy, sx = sx * 2 + 1, sy = sy * 2 + 1, dx = dx * 2 + 1, dy = dy * 2 + 1;
vector<tuple<int, int, int, int>> wall(k);
for (auto &[x1, y1, x2, y2] : wall)
cin >> x1 >> y1 >> x2 >> y2, x1 *= 2, y1 *= 2, x2 *= 2, y2 *= 2;
int ans = 15;
for (int i = 0; i < (1 << k); i++)
{
memset(ban, 0, sizeof(ban));
memset(vis, 0, sizeof(vis));
int cnt = 0;
for (int j = 0; j < k; j++)
{
if (i & (1 << j))
++cnt;
else
{
auto [x1, y1, x2, y2] = wall[j];
if (x1 == x2)
for (int y = min(y1, y2); y <= max(y1, y2); y++)
ban[x1][y] = 1;
else
for (int x = min(x1, x2); x <= max(x1, x2); x++)
ban[x][y1] = 1;
}
}
if (cnt >= ans)
continue;
queue<pair<int, int>> q;
q.emplace(sx, sy);
while (!q.empty())
{
auto [x, y] = q.front();
q.pop();
if (vis[x][y])
continue;
vis[x][y] = 1;
if (x == dx && y == dy)
{
ans = min(ans, cnt);
break;
}
for (int j = 0; j < 4; j++)
{
int nx = x + mx[j], ny = y + my[j];
if (nx >= 0 && nx <= n && ny >= 0 && ny <= m && !ban[nx][ny] && !vis[nx][ny])
q.emplace(nx, ny);
}
}
}
cout << ans << "\n";
}
return 0;
}
C. Backpack
DP
、暴力
考虑直接暴力dp,设
d
p
i
,
j
,
k
dp_{i,j,k}
dpi,j,k 表示对前
i
i
i 个物品,是否存在异或和为
j
j
j 且体积为
k
k
k 的方案,显然
d
p
i
,
j
,
k
=
d
p
i
−
1
,
j
,
k
∣
d
p
i
−
1
,
j
⊕
w
[
i
]
,
k
−
v
[
i
]
dp_{i,j,k}=dp_{i-1,j,k}|dp_{i-1,j \oplus w[i],k-v[i]}
dpi,j,k=dpi−1,j,k∣dpi−1,j⊕w[i],k−v[i],空间上可以滚动掉一维,但时间复杂度达到
O
(
N
3
)
O(N^3)
O(N3),太慢而无法通过。
考虑优化转移过程,对最后一维
k
k
k,如果用bitset直接移位,就无需遍历,时间复杂度降低至
O
(
N
3
w
)
O(\frac{N^3}{w})
O(wN3),可以通过。
本题关键在于想到枚举异或和,优化掉枚举体积,而不是反过来。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = (2 << 9) + 5, N = 2 << 9;
int v[MAXN], w[MAXN];
bitset<MAXN> lst[MAXN], now[MAXN];
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> v[i] >> w[i];
lst[0][0] = 1;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < N; j++)
now[j] = lst[j ^ w[i]] << v[i], now[j] |= lst[j];
for (int j = 0; j < N; j++)
lst[j] = now[j];
}
int ans = -1;
for (int i = N - 1; i >= 0; i--)
{
if (lst[i][m])
{
ans = i;
break;
}
}
cout << ans << "\n";
for (int i = 0; i < N; i++)
lst[i].reset();
}
return 0;
}
D. Ball
暴力
、排序
直接枚举点
O
(
N
3
)
O(N^3)
O(N3) 是不行的,考虑枚举边作为中位数,复杂度至少
O
(
N
2
)
O(N^2)
O(N2)。
对当前边,长度为
l
e
n
len
len,两端点为
a
,
b
a,b
a,b,如果从小到大加入边,则现在已知所有从
a
,
b
a,b
a,b 分别出发的,长度不大于
l
e
n
len
len 的边,因为是两两距离,则同样已知所有长度不小于
l
e
n
len
len 的边。用
b
s
[
v
]
bs[v]
bs[v] 记录从点
v
v
v 出发且已加入的边的另一端,若
l
e
n
len
len 是质数,则答案加上
b
s
[
a
]
⊕
b
s
[
b
]
bs[a] \oplus bs[b]
bs[a]⊕bs[b],用bitset优化,复杂度
O
(
N
3
w
)
O(\frac{N^3}{w})
O(wN3)。这是因为若一个点异或后值为1,一定同时存在一条已被加入的边和一条未被加入的边,所以该点可行,反之要么都已加入,要么都未加入,无效。
质数随便筛一下即可。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 2005;
int x[MAXN], y[MAXN], prime[200005];
bitset<MAXN> bs[MAXN];
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
prime[1] = 1;
for (int i = 2; i <= 200000; i++)
for (int j = i + i; j <= 200000; j += i)
prime[j] = 1;
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
vector<tuple<int, int, int>> e;
for (int i = 1; i <= n; i++)
cin >> x[i] >> y[i];
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
e.emplace_back(abs(x[i] - x[j]) + abs(y[i] - y[j]), i, j);
sort(e.begin(), e.end());
int ans = 0;
for (auto [len, a, b] : e)
{
if (!prime[len])
ans += (bs[a] ^ bs[b]).count();
bs[a][b] = bs[b][a] = 1;
}
cout << ans << "\n";
for (int i = 1; i <= n; i++)
bs[i].reset();
}
return 0;
}
E. Grammar
图论
待补
F. Travel plan
图论
、数学
、DP
首要一点是观察出所给图是一个仙人掌,这是因为题目保证图中不存在长为偶数的环。简要证明如下:
假设有一条边
E
E
E 是两个环的公共边之一,若
E
E
E 长度为奇数,则这两个环其他部分的边权和必须都是偶数。无论其他公共边长度之和是奇数还是偶数,两环形成的大环的环长都将为偶数,与题设矛盾。
E
E
E 长度为偶数时同理。故没有一条边在多个环中,该图为仙人掌图。
于是用莫比乌斯反演变为求
g
c
d
=
k
∗
i
(
k
≥
1
)
gcd=k*i \space (k \geq 1)
gcd=k∗i (k≥1) 的路径数,分别对
i
i
i 个图,每次只考虑边权为
k
∗
i
k*i
k∗i 的边,建出圆方树,在树上dp即可求得。具体地,转移过程如下图所示。
注:本题极度卡常,本地0.6s提交3.7s,使用前向星和快读才能卡过。
#include <bits/stdc++.h>
using namespace std;
constexpr int64_t MAXN = 2e5 + 5, MAXV = 1e5 + 5, mod = 998244353;
int n, m, L;
int st[MAXN], tp, low[MAXN], num[MAXN], dfn, nn;
int prime[MAXV], mu[MAXN];
bool vis[MAXV];
int64_t ans[MAXN], dp[MAXN];
vector<int> fac[MAXV];
vector<pair<int, int>> e[MAXN];
int head1[MAXN], head2[MAXN], tot1 = 1, tot2 = 1;
struct Edge
{
int to, next;
} e1[MAXN << 3], e2[MAXN << 3];
void add1(int u, int v) { e1[++tot1].to = v, e1[tot1].next = head1[u], head1[u] = tot1; }
void add2(int u, int v) { e2[++tot2].to = v, e2[tot2].next = head2[u], head2[u] = tot2; }
namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
inline int read()
{
int s = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar();
}
return s;
}
inline void write(int64_t x)
{
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
};
using namespace fastIO;
void init()
{
mu[1] = 1;
for (int i = 2; i < MAXV; i++)
{
if (!vis[i])
prime[++prime[0]] = i, mu[i] = -1;
for (int j = 1; j <= prime[0] && i * prime[j] < MAXV; j++)
{
vis[i * prime[j]] = 1;
if (i % prime[j] == 0)
{
mu[i * prime[j]] = 0;
break;
}
else
mu[i * prime[j]] = -mu[i];
}
}
for (int i = 1; i < MAXV; i++)
for (int j = i; j < MAXV; j += i)
fac[j].push_back(i);
}
void tarjan(int v) // 广义圆方树
{
low[v] = num[v] = ++dfn;
st[++tp] = v;
for (int i = head1[v]; i; i = e1[i].next)
{
int u = e1[i].to;
if (!num[u])
{
tarjan(u);
low[v] = min(low[u], low[v]);
if (low[u] >= num[v])
{
++nn;
int z;
do
{
z = st[tp--], add2(z, nn), add2(nn, z);
} while (z != u);
add2(v, nn), add2(nn, v);
}
}
else
low[v] = min(low[v], num[u]);
}
}
void dfs(int v, int fa, int id)
{
for (int i = head2[v]; i; i = e2[i].next)
{
int u = e2[i].to;
if (u != fa)
dfs(u, v, id);
}
if (v <= n) // 圆点
{
dp[v] = 1;
for (int i = head2[v]; i; i = e2[i].next)
{
int u = e2[i].to;
if (u == fa)
continue;
ans[id] = (ans[id] + dp[v] * dp[u] % mod) % mod;
dp[v] = (dp[v] + dp[u]) % mod;
}
}
else // 方点,看是否对应环
{
dp[v] = 0;
int deg = 0;
for (int i = head2[v]; i; i = e2[i].next)
++deg;
if (deg == 2) // 不对应,直接继承子结点dp值
{
for (int i = head2[v]; i; i = e2[i].next)
{
int u = e2[i].to;
if (u != fa)
dp[v] = dp[u];
}
}
else // 对应一个环,考虑除父结点外的环点对答案的贡献
{
for (int i = head2[v]; i; i = e2[i].next)
{
int u = e2[i].to;
if (u == fa)
continue;
ans[id] = (ans[id] + dp[v] * dp[u] % mod) % mod;
dp[v] = (dp[v] + 2 * dp[u] % mod) % mod;
}
}
}
}
int main()
{
init();
int t = read();
while (t--)
{
n = read(), m = read(), L = read();
while (m--)
{
int v = read(), u = read(), w = read();
for (auto i : fac[w])
e[i].emplace_back(v, u);
}
vector<int> vec;
for (int i = 1; i <= L; i++)
{
for (auto [v, u] : e[i])
{
add1(v, u), add1(u, v);
vec.push_back(v), vec.push_back(u);
}
nn = n, dfn = 0;
for (auto v : vec)
if (!num[v])
tp = 0, tarjan(v), dfs(v, 0, i);
for (auto v : vec)
head1[v] = head2[v] = num[v] = 0;
for (int i = nn; i > n; i--)
head2[i] = 0;
tot1 = tot2 = 1;
e[i].clear(), vec.clear();
}
int64_t res = 0;
for (int i = 1; i <= L; i++)
{
for (int j = 2; i * j <= L; j++)
ans[i] += (mu[j] * ans[i * j]) % mod + mod, ans[i] %= mod;
res ^= ans[i];
}
write(res), putchar('\n');
fill(ans + 1, ans + L + 1, 0);
}
flush();
return 0;
}
G. Treasure
图论
、数据结构
注意到询问要求在图中移动时经过的边权
w
≤
y
w \leq y
w≤y ,这实际上提示我们建出kruskal重构树,转换为树上问题解决。
由kruskal重构树性质,按边权从小到大排序时,重构树根结点对应最大边权,从下往上,点对应的边权非降。于是对于询问,就是倍增往上跳到符合边权要求的最高点
v
v
v,答案为
v
v
v 子树中所有种类最大值之和。
考虑直接在每个结点处维护答案。注意到每种类型的点不超过10个,对于一种类型,如果建出其虚树,可以发现父结点
f
a
fa
fa 和子结点
v
v
v 在原树路径上的所有点(不包括
f
a
fa
fa)同时从
v
v
v 得到贡献
m
x
[
v
]
mx[v]
mx[v],这是一个路径加,可以在树剖后用树状数组维护。但是码量巨大,可以考虑转换为树上差分,则查询变为子树求和。对于初始答案,在虚树上跑一遍dfs即可求得;维护答案时,考虑跑两遍dfs,一次消去先前贡献,一次在
v
a
l
[
x
]
+
=
y
val[x]+=y
val[x]+=y 后计算新贡献。
注:
本题极度卡常,本地1.8s左右可过,也许赛时机子更快会影响小一些。然而标程本地就要跑4s
因为树剖爬链多带一个log,虽然在本题中应该很小,但建议不要写路径加,而是转换成树上差分,这样树状数组也省去引入差分数组维护区间加。
虚树有两种建法,一种是两次排序,另一种是单调栈,后者效率更高。
很大的效率瓶颈来自每次更新都从零重建虚树,考虑分别对每种类型保存虚树边,需要时直接加入。然而虚树上使用vector存边也存在一些效率弊端,本题只有十几个点,却需要零散使用
2
e
5
2e5
2e5 大小的vector数组,实测提交用时4.9s,极限卡过。考虑直接开一个几十大小的前向星,边的cache非常连续,提交用时3.7s左右。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 2e5 + 5;
int n, m, q, c[MAXN];
int64_t val[MAXN], mx[MAXN];
vector<int> G[MAXN], col[MAXN];
namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
inline int read()
{
int s = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
s = s * 10 + ch - '0', ch = getchar();
return s;
}
inline void write(int64_t x)
{
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
};
using namespace fastIO;
namespace Kruskal
{
int tot, dsu[MAXN], ew[MAXN];
struct edge
{
int x, y, w;
edge(int x, int y, int w) : x(x), y(y), w(w) {}
bool operator<(const edge &rhs) { return w < rhs.w; }
};
vector<edge> e;
int find(int x) { return x == dsu[x] ? x : dsu[x] = find(dsu[x]); }
void kruskal()
{
tot = n;
iota(dsu + 1, dsu + n * 2 + 1, 1);
sort(e.begin(), e.end());
for (auto [v, u, w] : e)
{
v = find(v), u = find(u);
if (v != u)
{
dsu[v] = dsu[u] = ++tot, ew[tot] = w;
G[v].push_back(tot), G[tot].push_back(v);
G[u].push_back(tot), G[tot].push_back(u);
}
}
e.clear();
}
}
using namespace Kruskal;
namespace FenwickTree
{
int64_t sum[MAXN];
inline int lowbit(int x) { return x & (-x); }
void add(int pos, int64_t x)
{
for (; pos <= tot && pos; pos += lowbit(pos))
sum[pos] += x;
}
int64_t query_presum(int pos)
{
int64_t ans = 0;
for (; pos > 0; pos -= lowbit(pos))
ans += sum[pos];
return ans;
}
int64_t query_sum(int l, int r) { return query_presum(r) - query_presum(l - 1); }
}
using namespace FenwickTree;
namespace LCA
{
int num[MAXN], bottom[MAXN], dep[MAXN], father[MAXN][18], lg[MAXN], dfn;
void dfs(int v, int fa)
{
num[v] = ++dfn;
dep[v] = dep[fa] + 1;
father[v][0] = fa;
for (int i = 1; i <= 17; i++)
father[v][i] = father[father[v][i - 1]][i - 1];
for (auto u : G[v])
if (u != fa)
dfs(u, v);
bottom[v] = dfn;
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
while (dep[x] > dep[y])
x = father[x][lg[dep[x] - dep[y]] - 1];
if (x == y)
return x;
for (int k = lg[dep[x]] - 1; k >= 0; k--)
if (father[x][k] != father[y][k])
x = father[x][k], y = father[y][k];
return father[x][0];
}
int jump(int v, int y)
{
for (int i = 17; i >= 0; i--)
if (father[v][i] != 0 && ew[father[v][i]] <= y)
v = father[v][i];
return v;
}
}
using namespace LCA;
namespace VirtualTree
{
int st[25], tp;
int head[MAXN], tote = 1;
struct Edge
{
int to, next;
} le[25];
void adde(int u, int v) { le[++tote].to = v, le[tote].next = head[u], head[u] = tote; }
vector<pair<int, int>> E[MAXN];
void update(int v, int fa, int op)
{
mx[v] = head[v] == 0 ? val[v] : 0;
for (int i = head[v]; i; i = le[i].next)
{
int u = le[i].to;
update(u, v, op);
mx[v] = max(mx[v], mx[u]);
}
add(num[v], op * mx[v]), add(num[fa], -op * mx[v]);
}
void build(int color, int x, int y)
{
auto &ee = E[color];
if (y == 0)
{
auto &p = col[color];
sort(p.begin(), p.end(), [](int v, int u)
{ return num[v] < num[u]; });
st[tp = 1] = tot;
for (auto v : p)
{
if (v != tot)
{
auto LCA = lca(v, st[tp]);
if (LCA != st[tp])
{
while (num[LCA] < num[st[tp - 1]])
ee.emplace_back(st[tp - 1], st[tp]), --tp;
if (num[LCA] > num[st[tp - 1]])
ee.emplace_back(LCA, st[tp]), st[tp] = LCA;
else
ee.emplace_back(LCA, st[tp--]);
}
st[++tp] = v;
}
}
for (int i = 1; i < tp; i++)
ee.emplace_back(st[i], st[i + 1]);
}
for (auto [v, u] : ee)
adde(v, u);
if (y == 0)
update(tot, 0, 1);
else
update(tot, 0, -1), val[x] += y, update(tot, 0, 1);
for (auto [v, u] : ee)
head[v] = 0;
tote = 1;
}
}
using namespace VirtualTree;
int main()
{
for (int i = 1; i < MAXN; i++)
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
int t = read();
while (t--)
{
n = read(), m = read(), q = read();
for (int i = 1; i <= n; i++)
c[i] = read(), col[c[i]].push_back(i);
for (int i = 1; i <= n; i++)
val[i] = read();
while (m--)
{
int x = read(), y = read(), w = read();
e.emplace_back(x, y, w);
}
kruskal();
dfn = 0;
dfs(tot, 0);
for (int i = 1; i <= n; i++)
if (!col[i].empty())
build(i, 0, 0);
while (q--)
{
int op, x, y;
op = read(), x = read(), y = read();
if (op == 0)
build(c[x], x, y);
else
{
x = jump(x, y);
write(query_sum(num[x], bottom[x])), putchar('\n');
}
}
fill(sum + 1, sum + tot + 1, 0);
for (int i = 1; i <= tot; i++)
G[i].clear();
for (int i = 1; i <= n; i++)
col[i].clear(), E[i].clear();
}
flush();
return 0;
}
H. Path
图论
很明显的分层图最短路,关键只在于处理经过特殊边后的跳转。如果没有经过特殊边,直接正常走,否则对于所有非出点,如果从未跳转过,尝试更新最短距离和入队,然后标记其已被跳转过,对于出点,正常走,只是边权减去
k
k
k。
维护跳转点集需要频繁删除,可以考虑用set。由于最短路性质,每个点只会被跳转一次,因为后续跳转不会更优,因此正确性和复杂度都有保证。
注:本题卡常,换unordered_set或者pbds的hash_table,或者甚至你可以用链表;加fread快读。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
#define int int64_t
constexpr int MAXN = 1e6 + 5, INF = 1e18;
using tpl = tuple<int, int, int>;
vector<tpl> G[MAXN];
int n, m, s, k, d[MAXN][2], out[MAXN], num;
bool vis[MAXN][2];
namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
inline int read()
{
int s = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
s = s * 10 + ch - '0', ch = getchar();
return s;
}
inline void write(int x)
{
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
};
using namespace fastIO;
void dijkstra()
{
gp_hash_table<int, bool> ss;
for (int i = 1; i <= n; i++)
ss[i] = 1;
for (int i = 1; i <= n; i++)
vis[i][0] = vis[i][1] = 0, d[i][0] = d[i][1] = INF;
d[s][0] = 0;
priority_queue<tpl, vector<tpl>, greater<tpl>> q;
q.emplace(0, s, 0);
while (!q.empty())
{
auto [dis, v, stat] = q.top();
q.pop();
if (vis[v][stat])
continue;
vis[v][stat] = 1, ss.erase(v);
if (!stat)
{
for (auto [u, w, sp] : G[v])
if (!vis[u][sp] && dis + w < d[u][sp])
d[u][sp] = dis + w, q.emplace(d[u][sp], u, sp);
}
else
{
++num;
vector<int> del;
for (auto [u, _, __] : G[v])
out[u] = num;
for (auto [u, _] : ss)
{
if (out[u] != num)
{
if (dis < d[u][0])
d[u][0] = dis, q.emplace(dis, u, 0);
del.push_back(u);
}
}
for (auto u : del)
ss.erase(u);
for (auto [u, w, sp] : G[v])
if (!vis[u][sp] && dis + w - k < d[u][sp])
d[u][sp] = dis + w - k, q.emplace(d[u][sp], u, sp);
}
}
}
int32_t main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = read();
while (t--)
{
n = read(), m = read(), s = read(), k = read();
while (m--)
{
int x = read(), y = read(), w = read(), sp = read();
G[x].emplace_back(y, w, sp);
}
dijkstra();
for (int i = 1; i <= n; i++)
{
if (d[i][0] == INF && d[i][1] == INF)
write(-1), putchar(' ');
else
write(min(d[i][0], d[i][1])), putchar(' ');
G[i].clear();
}
putchar('\n');
}
flush();
return 0;
}
I. Laser
计算几何
如果有解,那么一个点一定在横线/竖线/45度直线/135度直线上,不妨直接对第一个点枚举这四种情况,对于不同的情况,可以通过旋转坐标轴来统一化判断过程。于是对当前情况,永远视作第一个点在横线上,只是所有点坐标变为旋转后的坐标,当找到第一个不在横线上的点时,枚举其在竖线/45度直线/135度直线上的情况,分别与横线有1个交点,则该交点就为对应情况下的中心点,检查该中心点是否合法即可。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 1e5 + 5;
int n, x[MAXN], y[MAXN], nx[MAXN], ny[MAXN];
bool check(int cx, int cy)
{
for (int i = 1; i <= n; i++)
{
int xx = nx[i] - cx, yy = ny[i] - cy;
if (!(xx == 0 || yy == 0 || xx + yy == 0 || xx - yy == 0))
return 0;
}
return 1;
}
bool judge()
{
for (int i = 1; i <= n; i++)
if (ny[i] != ny[1])
return check(nx[i], ny[1]) || check(nx[i] - (ny[i] - ny[1]), ny[1]) || check(nx[i] + (ny[i] - ny[1]), ny[1]);
return 1;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> x[i] >> y[i];
bool ok = 0;
// 枚举横线
for (int i = 1; i <= n; i++)
nx[i] = x[i], ny[i] = y[i];
ok |= judge();
// 枚举竖线
for (int i = 1; i <= n; i++)
nx[i] = y[i], ny[i] = x[i];
ok |= judge();
// 枚举45度直线
for (int i = 1; i <= n; i++)
nx[i] = x[i] - y[i], ny[i] = x[i] + y[i];
ok |= judge();
// 枚举135度直线
for (int i = 1; i <= n; i++)
nx[i] = x[i] + y[i], ny[i] = x[i] - y[i];
ok |= judge();
cout << (ok ? "YES\n" : "NO\n");
}
return 0;
}
J. Walk
数学
、分治
、DP
过难
K. Random
数学
可以认为所有数都是0.5,所以操作没有区别,那么答案就是 ( n − m ) / 2 (n-m)/2 (n−m)/2 的逆元。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
cout << (n - m) * 500000004ll % 1000000007ll << "\n";
}
return 0;
}
I. Alice and Bob
博弈论
假设只有一种数,容易发现,
c
n
t
0
≥
1
cnt_0 \geq 1
cnt0≥1 或
c
n
t
1
≥
2
cnt_1 \geq 2
cnt1≥2 或
c
n
t
2
≥
4
cnt_2 \geq 4
cnt2≥4 或
c
n
t
3
≥
8
cnt_3 \geq 8
cnt3≥8 等情况时Alice必胜,则数之间的大小转换显然。
推广到多种数,每次操作实际上要将集合等分,考虑类似1 2 2和2 2 3 3 3 3的情况,分法是1 | 2 2和2 2 | 3 3 3 3,最后实际上依然得到
c
n
t
0
≥
1
cnt_0 \geq 1
cnt0≥1,因此只需判断是否有
∑
i
=
0
n
c
n
t
i
2
i
≥
1
\sum_{i=0}^{n}\cfrac{cnt_i}{2^i} \geq 1
∑i=0n2icnti≥1,注意这里的除法不是下取整的,但由于范围巨大也不可能用浮点除法,不妨把位权乘上
2
n
2^n
2n,那么逆序加就行了。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
vector<int> a(n + 1);
for (auto &x : a)
cin >> x;
for (int i = n; i > 0; i--)
a[i - 1] += a[i] / 2;
cout << (a[0] ? "Alice\n" : "Bob\n");
}
return 0;
}