ZSTU2023校赛
篠塚真佑実的树
给定\(n\)个节点的树,其中\(m\)个节点存在传送门,当飞船经过存在传送门的节点的时候,可以选择无消耗地传送至其他存在传送门的节点,现在有\(q\)次询问,每次询问给出起点\(st\)和终点\(ed\),若每艘飞船在飞行中最多只能进行一次传送,请你输出每次询问从起点到终点的最短路径长度
\(1<=m<=n<=2e5,1<=q<=2e5\)
题解:树上倍增 + \(LCA\) + 最短路
我们可以将情况分为两类:
- 不进行传送:那么我们只要利用树上倍增求出起点和终点的\(lca\)即可,然后维护点到根节点距离即可快速求出起点和终点之间的最短距离
- 进行1次传送:我们可以把所有存在传送门的节点看成一个源点(有点类似缩点),然后利用\(dij\)求出其他点到传送门的最短距离\(dis\),那么利用一次传送后起点和终点之间的最短距离为:\(dis[st]+dis[ed]\)
我们对于这两种情况取\(min\)即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;
int n, m;
vector<pii> g[N];
int a[N];
int fa[N][22], dep[N];
int dis[N];
int dis1[N];
int vis[N];
int st[N];
void dfs(int u, int par, int w)
{
dep[u] = dep[par] + 1;
fa[u][0] = par;
dis[u] = dis[par] + w;
for (int i = 1; i <= 20; ++i)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (auto &[v, w] : g[u])
{
if (v == par)
continue;
dfs(v, u, w);
}
}
int lca(int u, int v)
{
if (dep[u] < dep[v])
swap(u, v);
for (int i = 20; i >= 0; i--)
{
if (dep[fa[u][i]] >= dep[v])
u = fa[u][i];
}
if (u == v)
return u;
for (int i = 20; i >= 0; i--)
{
if (fa[u][i] != fa[v][i])
{
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
void dij()
{
for (int i = 1; i <= n; ++i)
dis1[i] = INF, vis[i] = 0;
priority_queue<pii, vector<pii>, greater<pii>> q;
for (int i = 1; i <= m; ++i)
{
int v = a[i];
dis1[v] = 0;
q.push({dis1[v], v});
}
while (q.size())
{
int u = q.top().second;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
for (auto [v, w] : g[u])
{
if (dis1[v] > dis1[u] + w)
{
dis1[v] = dis1[u] + w;
q.push({dis1[v], v});
}
}
}
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= m; ++i)
cin >> a[i];
for (int i = 1, u, v, w; i < n; ++i)
{
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dij();
dfs(1, 0, 0);
int q;
cin >> q;
while (q--)
{
int s, ed;
cin >> s >> ed;
int ans = INF;
int rt = lca(s, ed);
ans = min(ans, dis[s] + dis[ed] - 2 * dis[rt]);
int d = dis1[s] + dis1[ed];
ans = min(ans, d);
cout << ans << endl;
}
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
树上Minmax
给定一棵树,其包含 \(n\) 个结点和 \(n − 1\) 条边,每条边连接两个结点,且任意两个结点间有且仅有一条简单路径互相可达,每个点存在点权\(a_i\)
每次操作可以断一条边,随后选择分裂出的两棵树中的一棵树删除,请最小化操作次数使得最后剩下的树的点权和最大。\(1<=n<=4*10^5\)
\(-10^9<=a_i<=10^9\)
题解:树形\(dp\)
不妨令根为\(1\),我们维护每个节点子树中的最大点权和以及得到该最大点权和所需的最小操作次数,对于任意节点\(u\)来说,存在\(3\)种情况:
- 子树\(v\)中的最大点权和\(<0\),显然我们要维护的是\(u\)子树内最大的点权和,所以如果我们不断开\(u\)和\(v\)之间的边,那么最大点权和会变小,所以我们贪心的将其断开,操作次数加\(1\)
- 子树\(v\)的最大点权和\(>0\),那么我们贪心的将其收入囊中,我们不选择断开,所以\(u\)节点需要加上\(v\)节点的最大点权和以及将\(v\)节点变成最大点权和的操作次数
- 子树\(v\)的最大点权和\(=0\),显然对于\(u\)的最大点权和来说我要不要都行,但是我们需要最小化操作次数,所以如果我们删去\(u\)和\(v\)之间的边,操作次数加\(1\),如果说子树\(v\)中的最小操作次数\(<1\),我们可以选择不删除这条边,否则我们为了维护最小操作次数选择删除\(u\)和\(v\)之间的边
那么最大我们遍历所有节点子树中最大的点权和,如果节点\(u\)中存在最大点权和,但是\(u\)不是根节点,我们需要断开其和根节点的一条边,使其成为连通块;如果\(u\)本身就是根节点,那么不受影响
状态表示:\(f[u][0/1]\):\(0\)代表\(u\)节点子树中的最大点权和,1代表最小操作次数
状态转移:按照上面分析的情况转移即可
if (f[v][0] < 0) f[u][1]++; else if (f[v][0] > 0) { f[u][0] += f[v][0]; f[u][1] += f[v][1]; } else if (f[v][0] == 0) { if (f[v][1] < 1) { f[u][0] += f[v][0]; f[u][1] += f[v][1]; } else f[u][1]++; }
状态初始:\(f[u][0] = a_u,f[u][1] = 0\)
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 4e5 + 10, M = 4e5 + 10;
int n;
int a[N];
int f[N][2];
vector<int> g[N];
void dfs(int u, int par)
{
f[u][0] = a[u];
f[u][1] = 0;
for (auto v : g[u])
{
if (v == par)
continue;
dfs(v, u);
if (f[v][0] < 0)
f[u][1]++;
else if (f[v][0] > 0)
{
f[u][0] += f[v][0];
f[u][1] += f[v][1];
}
else if (f[v][0] == 0)
{
if (f[v][1] > 1)
f[u][1]++;
else
{
f[u][0] += f[v][0];
f[u][1] += f[v][1];
}
}
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1, u, v; i < n; ++i)
{
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
int maxx = -INF;
for (int i = 1; i <= n; ++i)
{
maxx = max(f[i][0], maxx);
}
int cnt = INF;
for (int i = 1; i <= n; ++i)
{
if (f[i][0] == maxx)
{
if (i != 1)
cnt = min(cnt, f[i][1] + 1);
else
cnt = min(cnt, f[i][1]);
}
}
cout << maxx << " " << cnt << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
树上MinmaxⅡ
给定一棵根为 \(1\) 的树,其包含 \(n\) 个结点和 \(n − 1\) 条边,每条边连接两个结点,且任意两个结点间有且仅有一条简单路径互相可达。在这里,我们认为当 \(n=1\) 时,根节点也是叶节点。
我们定义一个子节点都是叶节点且子树都被打上标记的节点为 “枝” ,被标记的叶节点都是 “花” 。在任何时刻你都可以选择一个 “枝” ,把它的所有子节点,也即 “花” 折下来。操作过后,会删除选定的 “枝” 和它的 “花” 间的连边, 选定的 “枝” 会变成新的 “花” 。
在每个时间点你都可以选取一个没有被标记过的节点来打上标记,最开始没有节点被标记。
你的目标是将树变为一枝 “花” 并最小化任何时间点树上被标记点数量的最大值,并求出该最小化后的最大值
题解:树形\(dp\) + 贪心 \(O(nlogn)\)
首先如果要将任意节点\(u\)变为花节点,我们必须将其所有子节点\(v\)变为花节点,即叶子节点,然后将\(u\)节点打上标记后\(u\)就变成了枝节点,然后我们就可以将花节点\(v\)折下使得\(u\)变为花节点;我们发现我们在将\(u\)的子节点\(v\)变为花的过程中,假设\(u\)有\(k\)个子节点,我们不妨先将子节点\(v_1\)变为花,那么在将\(v_2\)变为花的时候\(v_1\)一直处于标记状态,对于\(v_3\)来说,将其变为花的时候,\(v_1,v_2\)处于标记状态,令\(f[u]\)为将\(u\)节点变为花的所需的被标记点的数量,那么我们可以得到规律:将\(v_i\)变为花的时刻树上被标记的节点数位\(f[v_i]+i-1\),那儿我们是要最小化树上任意时刻被标记点的最大值,我们只需贪心地先将最大的子树变为花,然后次大.....最后选择最小的子树变为花即可,对于贪心我们只需对子节点的\(dp\)值进行排序即可
注意最后将\(u\)的所有子节点变为花后,我们还需要标记\(u\),那么此时场上被标记的节点数为\(sz[u]+1\),\(sz[u]\)代表\(u\)的子节点数
状态表示:\(f[u]\):将\(u\)节点变为花的所需的被标记点的最大数量
状态属性:\(MAX\)
状态转移:
\[f[u] = max(f[v_1],f[v_2]+1...f[v_i]+i-1,sz[u]+1)\\ f[v_1]>=f[v_2]>=...f[v_i] \]状态初始:\(f[u] = 1\)
答案呈现:遍历\(f[u]\)后找到最大值即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 4e5 + 10, M = 4e5 + 10;
int n;
int f[N];
vector<int> g[N];
void dfs(int u, int par)
{
f[u] = 1;
vector<int> a;
for (auto v : g[u])
{
if (v == par)
continue;
dfs(v, u);
a.push_back(f[v]);
}
sort(all(a), greater<int>());
for (int i = 0; i < a.size(); ++i)
f[u] = max(f[u], a[i] + i);
f[u] = max(f[u], (long long)(a.size() + 1));
a.clear();
}
void solve()
{
cin >> n;
for (int i = 1, u, v; i < n; ++i)
{
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
int ans = -INF;
for (int i = 1; i <= n; ++i)
ans = max(ans, f[i]);
cout << ans << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
千里朱音的数
\[(\sum_{p_1\times p_2\times p_3<=k,1<=p_1,p_2,p_3<=n,p_1,p_2,p_3∈N^+} p_1\times p_2\times p_3)\ mod \ 998244353\\ 1<=n,k<=10^9 \]
题解:思维 + 数论 : 还是蛮有套路的,多学学
不妨我们令\(p_1<=p_2<=p_3\)
此时我们发现\(p_1\)的上限为\(min(\sqrt[3]{k},n)\),即\(p_1\)的范围\([1,min(\sqrt[3]{k},n)]\)那么\(p_2\)的上限为\(min(\sqrt{\frac{k}{p_1}},n)\),即\(p_2\)的范围为\([p_1,min(\sqrt{\frac{k}{p_1}},n)]\),那么对于\(p_3\)来说上限为\(min(\frac{k}{p_1p_2},n)\),即\(p_3\)范围为\([p_2,min(\frac{k}{p_1p_2},n)]\)
我们打表后发现可以利用求和公式快速算出\(p_3\)的贡献:\(p_1*p_2*\sum p_3\),所以我们只需要枚举\(p_1,p_2\)即可,时间复杂度为\(O(k^{\frac{2}{3}})\)
但是我们只枚举了\(p_1<=p_2<=p_3\),实际上少计算了答案,我们考虑以下三种情况:
- 若\(p_1=p_2=p_3\),此时我们没有少算答案,不需要多加
- 若\(p_1<=p_2=p_3\ or\ p_1=p_2<=p_3\),对于这两种情况我们需要对贡献乘以\(C_3^1\)
- 若\(p_1<p_2<p_3\),对于这种情况我们需要对贡献乘\(A_3^3\)
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;
int n, k;
void solve()
{
cin >> n >> k;
int ans = 0;
for (int p1 = 1; p1 <= min((int)cbrt(k), n); ++p1)
{
for (int p2 = p1; p2 <= min((int)sqrt(1.0 * k / p1), n); ++p2)
{
int L = p2;
int R = min((int)(1.0 * k / (p1 * p2)), n);
if (R >= L)
{
if (p1 == p2)
{
ans = (ans % mod + ((p1 % mod) * (p2 % mod) * (p2 % mod)) % mod) % mod;
if (R >= L + 1)
ans = (ans % mod + ((p1 % mod) * (p2 % mod) * (((L + 1 + R) * (R - L) / 2) % mod) * 3) % mod) % mod;
}
else if (p1 < p2)
{
ans = (ans % mod + ((p1 % mod) * (p2 % mod) * (p2 % mod) * 3) % mod) % mod;
if (R >= L + 1)
ans = (ans % mod + ((p1 % mod) * (p2 % mod) * (((L + 1 + R) * (R - L) / 2) % mod) * 6) % mod) % mod;
}
}
}
}
cout << ans << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}