2023冲刺国赛模拟 2.1
T1 树
首先考虑初始节点只有 \(1\) 个的情况,很容易使用 dp 解决,设 \(f_i\) 表示初始节点为 \(i\) ,占领以 \(i\) 为根的子树所需要的最小回合数量,只需要优先占领回合多的子树即可。
当初始节点为 \(2\) 个时,容易发现 \(u,v\) 路径上存在一条边,满足最优方案下 \(u,v\) 的士兵均不会跨过这条边,因此可以枚举这条边并对边两侧的子树分别 dp ,容易发现 \(u,v\) 两侧的 dp 值随着边位置的单调移动具有单调性,因此可以二分两侧 dp 值最接近的位置,这个位置取到最优方案。
直接做复杂度为 \(O(n\log^2 n)\) ,考虑进行优化,容易发现每次进行 dp 进行了许多重复的计算,因此考虑断掉链上所有边预处理每个地方的 dp 值,二分 check 时只需要连接链即可,此时我们相当于在原先节点下连接一棵子树,设当前连接的子树的贡献为 \(res\) ,此时我们需要将 \(res\) 插入到对应节点的子树序列中,我们设原先这个节点 \(i\) 取到最大 dp 值的位置为 \(pos\) ,如果 \(res\) 可以插入到 \(pos\) 之前,显然会产生 \(f_i+1\) 的贡献,否则产生 \(f_i\) 的贡献,同时考虑到 \(res\) 插入到序列开头贡献为 \(res+1\) ,对这几种贡献取 \(\max\) 即可,复杂度为 \(O(n\log n)\) 。
code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;
const int max1 = 5e5, B = 30;
const int inf = 0x3f3f3f3f;
int n, a, b;
struct Node
{ int next, v; } edge[max1 * 2 + 5];
int head[max1 + 5], total;
int father[max1 + 5], seq[max1 + 5], len;
bool vis[max1 + 5];
int f[max1 + 5], maxpos[max1 + 5], ans;
vector <int> tmp;
void Add ( int u, int v )
{
edge[++total].v = v;
edge[total].next = head[u];
head[u] = total;
return;
}
void Dfs ( int now, int fa, int pre_edge )
{
father[now] = fa;
for ( int i = head[now]; i; i = edge[i].next )
{
int v = edge[i].v;
if ( v == fa )
continue;
Dfs(v, now, i);
}
return;
}
void Redfs ( int now, int fa )
{
for ( int i = head[now]; i; i = edge[i].next )
{
int v = edge[i].v;
if ( v == fa || vis[v] )
continue;
Redfs(v, now);
}
tmp.clear();
for ( int i = head[now]; i; i = edge[i].next )
{
int v = edge[i].v;
if ( v == fa || vis[v] )
continue;
tmp.push_back(f[v]);
}
sort(tmp.begin(), tmp.end());
reverse(tmp.begin(), tmp.end());
f[now] = 0; maxpos[now] = -1;
int siz = tmp.size();
for ( int i = 0; i < siz; i ++ )
{
if ( f[now] <= i + 1 + tmp[i] )
{
f[now] = i + 1 + tmp[i];
maxpos[now] = tmp[i];
}
}
return;
}
bool Check ( int mid )
{
int A = f[seq[mid + 1]], B = f[seq[mid]];
for ( int i = mid + 2; i <= len; i ++ )
A = max(f[seq[i]] + ( A >= maxpos[seq[i]] ), A + 1);
for ( int i = mid - 1; i >= 1; i -- )
B = max(f[seq[i]] + ( B >= maxpos[seq[i]] ), B + 1);
return A < B;
}
int Query ( int mid )
{
int A = f[seq[mid + 1]], B = f[seq[mid]];
for ( int i = mid + 2; i <= len; i ++ )
A = max(f[seq[i]] + ( A >= maxpos[seq[i]] ), A + 1);
for ( int i = mid - 1; i >= 1; i -- )
B = max(f[seq[i]] + ( B >= maxpos[seq[i]] ), B + 1);
return max(A, B);
}
int main ()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
scanf("%d%d%d", &n, &a, &b); total = 1;
for ( int i = 2, u, v; i <= n; i ++ )
{
scanf("%d%d", &u, &v);
Add(u, v), Add(v, u);
}
Dfs(a, 0, 0);
int now = b;
while ( now )
{
seq[++len] = now;
vis[now] = true;
now = father[now];
}
for ( int i = 1; i <= len; i ++ )
Redfs(seq[i], 0);
int L = 1, R = len - 1, pos = len - 1;
while ( L <= R )
{
int mid = L + R >> 1;
if ( Check(mid) )
pos = mid, R = mid - 1;
else
L = mid + 1;
}
ans = Query(pos);
if ( pos > 1 )
ans = min(ans, Query(pos - 1));
printf("%d\n", ans);
return 0;
}
T2 最小生成树
假设当前我们已经知道所有树边权值的相对大小关系,考虑统计非树边产生的贡献。
有一个经典结论:非树边连接端点 \(u,v\) 满足非树边权值大于 \(u,v\) 对应最小生成树的路径上边权最大值。
从边权较小的边向较大的边连边,可以得到拓扑关系图,去掉不必要的边后发现树边构成一条链,链上每个节点连接一些非树边,此时我们的目的是统计拓扑序的方案数,设每个节点下连接的非树边个数为 \(w_i\) ,考虑按照 \(w_i\) 个非树边, \(1\) 个树边的顺序依次插入到拓扑序中。
设当前加入的边的总数为 \(n\) ,树边有 \(b\) 个,方案数为 \(c\) 个,所有方案的树边位置和为 \(s\) 。
考虑加入一条树边,由于只能插入到序列开头,因此变化为:
考虑加入一条非树边,变化为:
简单解释一下 \(s\to s(n+2)\) ,首先一个基础的方案数的贡献为 \(s(n+1)\) ,之后树边所在的位置 \(A\) ,容易发现存在 \(A\) 种方案满足这个树边的位置 \(+1\) ,不难发现这部分的贡献和为 \(s\) 。
考虑加入 \(w_i\) 条非树边,变化为:
容易发现上述过程可以使用状压 dp 优化为 \(O(n2^n)\) 。
考虑快速求解 \(w_i\) ,容易发现 \(w_i\) 为添加边 \(i\) 后新增的满足端点位于同一连通块内的非树边的数量,考虑预处理 \(cnt_S\) 表示加入 \(S\) 集合内的树边后满足端点位于同一连通块内的非树边数量,此时很容易查询得到 \(w_i\) ,单独考虑每个非树边的贡献,设非树边连接两端点对应的树上路径的边集为 \(S\) ,容易发现这会贡献到 \(cnt_T,S\subseteq T\) ,使用 FWT 可以 \(O(n\log n)\) 预处理。
code
#include <cstdio>
#include <algorithm>
using namespace std;
const int max1 = 20, max2 = 400, max3 = 1 << 20;
const int mod = 1e9 + 7;
int n, m, u[max2 + 5], v[max2 + 5];
int inv[max2 + 5], fac[max2 + 5], ifac[max2 + 5];
struct Node
{ int next, v; } edge[max1 * 2 + 5];
int head[max1 + 5], total;
int father[max1 + 5], pre[max1 + 5], deep[max1 + 5];
int cnt[max3 + 5], f[max3 + 5], g[max3 + 5];
void Add ( int &x, int y )
{
x += y;
if ( x >= mod )
x -= mod;
return;
}
void Add_Edge ( int u, int v )
{
edge[++total].v = v;
edge[total].next = head[u];
head[u] = total;
return;
}
void Dfs ( int now, int fa, int pre_edge )
{
father[now] = fa, pre[now] = pre_edge, deep[now] = deep[fa] + 1;
for ( int i = head[now]; i; i = edge[i].next )
{
int v = edge[i].v;
if ( v == fa )
continue;
Dfs(v, now, i >> 1);
}
return;
}
int Get_Edge ( int u, int v )
{
int p = u, q = v;
while ( p != q )
{
if ( deep[p] < deep[q] )
swap(p, q);
p = father[p];
}
int s = 0;
while ( u != p )
{
s |= 1 << pre[u] - 1;
u = father[u];
}
while ( v != p )
{
s |= 1 << pre[v] - 1;
v = father[v];
}
return s;
}
int main ()
{
freopen("mst.in", "r", stdin);
freopen("mst.out", "w", stdout);
scanf("%d%d", &n, &m);
for ( int i = 1; i <= m; i ++ )
scanf("%d%d", &u[i], &v[i]);
total = 1;
for ( int i = 1; i <= n - 1; i ++ )
Add_Edge(u[i], v[i]), Add_Edge(v[i], u[i]);
deep[0] = -1;
Dfs(1, 0, 0);
for ( int i = n; i <= m; i ++ )
cnt[Get_Edge(u[i], v[i])] = 1;
int lim = 1 << n - 1;
for ( int i = 0; i <= n - 2; i ++ )
for ( int j = 0; j < lim; j += 1 << i + 1 )
for ( int k = 0; k < 1 << i; k ++ )
cnt[( 1 << i ) + j + k] += cnt[j + k];
inv[1] = 1;
for ( int i = 2; i <= m; i ++ )
inv[i] = 1LL * ( mod - mod / i ) * inv[mod % i] % mod;
fac[0] = ifac[0] = 1;
for ( int i = 1; i <= m; i ++ )
{
fac[i] = 1LL * fac[i - 1] * i % mod;
ifac[i] = 1LL * ifac[i - 1] * inv[i] % mod;
}
f[lim - 1] = 0, g[lim - 1] = 1;
for ( int s = lim - 1; s >= 0; s -- )
{
for ( int i = 1; i <= n - 1; i ++ )
{
if ( s >> i - 1 & 1 )
{
int w = cnt[s] - cnt[s ^ 1 << i - 1], sum = m - cnt[s] - __builtin_popcount(s);
int upf = 1LL * f[s] * fac[sum + w + 1] % mod * ifac[sum + 1] % mod, upg = 1LL * g[s] * fac[sum + w] % mod * ifac[sum] % mod;
Add(f[s ^ 1 << i - 1], ( upf + 1LL * upg * ( n - __builtin_popcount(s) ) ) % mod);
Add(g[s ^ 1 << i - 1], upg);
}
}
}
printf("%d\n", f[0]);
return 0;
}
T3 矩阵
容易发现 \(1\) 操作只会影响 \(y\) 坐标, \(2\) 操作只会影响 \(x\) 坐标,考虑一次询问 \((x,y)\) ,分别找到 \(x,y\) 所对应的操作时间 \(timx, timy\) ,容易发现 \((x,y)\) 位于第 \(\min(timx, timy)\) 次操作产生的行 / 列上,假设 \(timx<timy\) ,我们只需要找到 \(x\) 坐标在 \(timy\) 时刻的排名,容易发现这是对历史信息的查询,因此考虑可持久化平衡树,找到当前时刻 \(x\) 坐标所对应的节点,我们需要在 \(timy\) 时刻这个节点所对应的排名,由于可持久化后节点的 father 信息会发生改变,因此我们需要自上而下查询排名,考虑对于每个节点按照中序遍历维护一个权值,此时我们就是在 \(timy\) 时刻的平衡树上查询权值 \(val\) 的排名。由于存在插入行 / 列的操作,因此需要动态维护权值(否则 double 精度会爆炸),使用替罪羊树维护这个权值即可。
code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <iostream>
#include <random>
using namespace std;
const int max1 = 1e5, maxlen = 3e5;
const double alpha = 0.80, lim = 1e9, eps = 1e-10;
const unsigned int seed = 20051107;
mt19937 rnd((unsigned long long)&seed);
int Sint ( int L, int R )
{ return uniform_int_distribution <int> (L, R) (rnd); }
int n, p, q;
struct Scapegoat_Tree
{
#define lson(now) tree[now].son[0]
#define rson(now) tree[now].son[1]
struct Struct_Scapegoat_Tree
{
int son[2], size;
double val;
}tree[max1 * 7 + 5];
int root, total;
int s[max1 * 7 + 5], top;
void Clear ()
{
lson(0) = rson(0) = tree[0].size = tree[0].val = root = total = 0;
return;
}
void Push_Up ( int now )
{
tree[now].size = tree[lson(now)].size + tree[rson(now)].size + 1;
return;
}
bool Check ( int now )
{
return max(tree[lson(now)].size, tree[rson(now)].size) > tree[now].size * alpha;
}
void Dfs ( int now )
{
if ( !now )
return;
Dfs(lson(now));
s[++top] = now;
Dfs(rson(now));
return;
}
int ReBuild ( int L, int R, double dl, double dr )
{
if ( L > R )
return 0;
int mid = L + R >> 1;
tree[s[mid]].val = 0.5 * ( dl + dr );
lson(s[mid]) = ReBuild(L, mid - 1, dl, tree[s[mid]].val);
rson(s[mid]) = ReBuild(mid + 1, R, tree[s[mid]].val, dr);
Push_Up(s[mid]);
return s[mid];
}
int Build ( int L, int R, double dl, double dr )
{
if ( L > R )
return 0;
int mid = L + R >> 1;
int now = ++total;
tree[now].val = 0.5 * ( dl + dr );
lson(now) = Build(L, mid - 1, dl, tree[now].val);
rson(now) = Build(mid + 1, R, tree[now].val, dr);
Push_Up(now);
return now;
}
void Insert ( int &now, int k, double L, double R )
{
if ( !now )
{
now = ++total;
lson(now) = rson(now) = 0;
tree[now].size = 1;
tree[now].val = 0.5 * ( L + R );
return;
}
if ( tree[lson(now)].size >= k )
Insert(lson(now), k, L, tree[now].val);
else
Insert(rson(now), k - tree[lson(now)].size - 1, tree[now].val, R);
if ( Check(now) )
{ top = 0; Dfs(now); now = ReBuild(1, top, L, R); }
Push_Up(now);
return;
}
int Insert ( int k )
{
Insert(root, k, -lim, lim);
return total;
}
double Query ( int now )
{ return tree[now].val; }
};
struct FHQ_Treap
{
#define lson(now) tree[now].son[0]
#define rson(now) tree[now].son[1]
struct Struct_FHQ_Treap
{
int son[2], rd;
int tim, id, size;
}tree[max1 * 200 + 5];
int root[max1 + 5], total;
Scapegoat_Tree Map;
void Clear ()
{
lson(0) = rson(0) = tree[0].rd = tree[0].tim = tree[0].id = tree[0].size = root[0] = total = 0;
Map.Clear();
return;
}
void Push_Up ( int now )
{
tree[now].size = tree[lson(now)].size + tree[rson(now)].size + 1;
return;
}
void Split ( int now, int k, int &x, int &y )
{
if ( !now )
{ x = y = 0; return; }
int clone = ++total;
tree[clone] = tree[now];
if ( tree[lson(clone)].size >= k )
y = clone, Split(lson(clone), k, x, lson(y));
else
x = clone, Split(rson(clone), k - tree[lson(clone)].size - 1, rson(x), y);
Push_Up(clone);
return;
}
int Merge ( int x, int y )
{
if ( !x || !y )
{
if ( !x && !y )
return 0;
int clone = ++total;
tree[clone] = tree[x | y];
return clone;
}
if ( tree[x].rd > tree[y].rd )
{
int clone = ++total;
tree[clone] = tree[x];
rson(clone) = Merge(rson(clone), y);
Push_Up(clone);
return clone;
}
int clone = ++total;
tree[clone] = tree[y];
lson(clone) = Merge(x, lson(clone));
Push_Up(clone);
return clone;
}
void Insert ( int num, int k, int tim )
{
k += maxlen;
int x, y;
Split(root[num], k - 1, x, y);
int New = ++total;
lson(New) = rson(New) = 0;
tree[New].rd = Sint(0, 2e9);
tree[New].tim = tim;
tree[New].id = Map.Insert(tree[x].size);
tree[New].size = 1;
root[num] = Merge(Merge(x, New), y);
return;
}
int Build ( int L, int R, int tim )
{
if ( L > R )
return 0;
int mid = L + R >> 1;
int now = ++total;
tree[now].rd = Sint(0, 2e9);
tree[now].tim = tim;
tree[now].id = now;
lson(now) = Build(L, mid - 1, tim);
rson(now) = Build(mid + 1, R, tim);
Push_Up(now);
return now;
}
void Build ()
{
root[0] = Build(1, maxlen + maxlen + 1, 0);
Map.root = Map.Build(1, maxlen + maxlen + 1, -lim, lim);
return;
}
int Kth_size ( int now, int k )
{
if ( k == tree[lson(now)].size + 1 )
return now;
else if ( k <= tree[lson(now)].size )
return Kth_size(lson(now), k);
return Kth_size(rson(now), k - tree[lson(now)].size - 1);
}
int Kth_val ( int now, double d )
{
if ( fabs(Map.Query(tree[now].id) - d) < eps )
return tree[lson(now)].size + 1;
if ( Map.Query(tree[now].id) > d )
return Kth_val(lson(now), d);
return tree[lson(now)].size + 1 + Kth_val(rson(now), d);
}
int Kth ( int num, int v, int k )
{
k += maxlen;
int u = Kth_size(root[num], k);
return Kth_val(root[v], Map.Query(tree[u].id));
}
int Query_Tim ( int now, int k )
{
if ( k == tree[lson(now)].size + 1 )
return tree[now].tim;
if ( k <= tree[lson(now)].size )
return Query_Tim(lson(now), k);
return Query_Tim(rson(now), k - tree[lson(now)].size - 1);
}
int Query ( int num, int k )
{
k += maxlen;
return Query_Tim(root[num], k);
}
}Treex, Treey;
void Query ( int num, int x, int y )
{
int timx = Treex.Query(num, x);
int timy = Treey.Query(num, y);
if ( timx > timy )
{
p = timx, q = Treey.Kth(num, timx, y) - maxlen;
printf("%d %d\n", p, q);
}
else if ( timx < timy )
{
p = Treex.Kth(num, timy, x) - maxlen, q = timy;
printf("%d %d\n", p, q);
}
else
{
p = Treex.Kth(num, 0, x) - maxlen, q = Treey.Kth(num, 0, y) - maxlen;
printf("%d %d\n", p, q);
}
return;
}
int main ()
{
freopen("matrix.in", "r", stdin);
freopen("matrix.out", "w", stdout);
int opt, x, y;
scanf("%d", &n);
Treex.Clear(), Treey.Clear();
Treex.Build(), Treey.Build();
p = q = 0;
for ( int i = 1; i <= n; i ++ )
{
Treex.root[i] = Treex.root[i - 1];
Treey.root[i] = Treey.root[i - 1];
scanf("%d%d", &opt, &x);
if ( opt == 1 )
{
x += p + q;
Treey.Insert(i, x, i);
}
else if ( opt == 2 )
{
x += p + q;
Treex.Insert(i, x, i);
}
else
{
scanf("%d", &y);
x += p + q, y += p - q;
Query(i, x, y);
}
}
return 0;
}

浙公网安备 33010602011771号