10月15日模拟赛题解

10月15日模拟赛题解

A 树

Description

给定一棵 n 个节点的树,每个节点有两个参数 a, b,对于每个节点,求子树中参数为 b 的所有节点的 a 之和

Limitations

100% 1bn105, a1000

60% 1bn1000

30% 1b,n10

Solution

对于 30% 的数据,我也不会做。

对于 60% 的数据,考虑枚举一下 b,然后 O(n) 在树上 dfs 即可。

考虑满分做法。

一个显而易见的想法是,对于一个 bi,我们遍历他子树中参数 bbi 不同的节点是没有意义的,因此我们考虑遍历完一个节点以后直接跳到它子树内最靠近它的 b 相同的节点,换句话说,对每个节点,寻找一个与它参数 b 相同的最近的祖先作为父节点。

考虑在 dfs 的时候,如果我们对每个 b 维护根到当且节点的链上参数为 b 的节点序列,则当前节点的同参数父节点就是对应序列的最后一个节点。考虑维护这个节点序列只需要开 n 个栈分别维护每个 b 的序列即可,即 dfs 到一个节点时将节点入栈,离开时弹出,这样显然可以保证栈内元素是有序的根到当前节点链上的节点序列。

然后我们相当于对于每个 b 都新建了一棵树,在这些树上做 dfs 即可。事实上在原来的树上做 dfs 的时候只要维护好每个节点在新树上的孩子就可以直接统计,不需要显式地建出新树。

时空复杂度 O(n),期望得分 100 pts

Code

#include <cstdio>
#include <stack>
#include <vector>

const int maxn = 100010;

struct Edge {
  int v;
  Edge *nxt;

  Edge(const int _v, Edge *h) : v(_v), nxt(h) {}
};
Edge *hd[maxn];

int n;
int b[maxn], ans[maxn];
bool vis[maxn];
std::stack<int>stk[maxn];
std::vector<int>son[maxn];

void dfs(const int u);

int main() {
  freopen("tree.in", "r", stdin);
  freopen("tree.out", "w", stdout);
  qr(n);
  for (int i = 1; i <= n; ++i) {
    qr(ans[i]); qr(b[i]);
  }
  for (int i = 1, u, v; i < n; ++i) {
    u = v = 0; qr(u); qr(v);
    hd[u] = new Edge(v, hd[u]);
    hd[v] = new Edge(u, hd[v]);
  }
  dfs(1);
  for (int i = 1; i <= n; ++i) {
    qw(ans[i], i == n ? '\n' : ' ', true);
  }
  return 0;
}

void dfs(const int u) {
  vis[u] = true;
  int k = b[u];
  if (stk[k].size()) {
    son[stk[k].top()].push_back(u);
  }
  stk[k].push(u);
  for (auto e = hd[u]; e; e = e->nxt) if (!vis[e->v]) {
    int v = e->v;
    dfs(v);
  }
  for (auto v : son[u]) {
    ans[u] += ans[v];
  }
  stk[k].pop();
}

B 小偷

Description

有一个长度不低于 4 的序列 A,满足 AiAi1=k,其中 k 是一个正整常数,且序列的最大值不超过 n

显然这样的合法序列有许多个,现在我们对序列的参数一无所知,只知道合法的序列有 m 个,求 n 最小是多少。无解输出 -1

Limitations

100% 3m1015

60% 3m100000

30% 3m1000

Solution

对于前 30% 的数据,爆搜一下序列即可。

对于前 60% 的数据,枚举一下 k 即可,然后枚举序列长度即可。值得注意的是由于序列每次除以 k,因此序列长度是 O(log) 级别的,并且序列最大值大概是 O(m) 级别,最大值至少除 3k 后的结果必须是正数,因此 k 也不会很大,大概在 102 数量级,因此枚举可过。时间复杂度 O(mlogm)

至于证明最大值是 O(m) 的……我是打个表看出来的(雾

对于全部的数据,考虑求最小值,并且显然不可能贪心,DP由于序列长度不确定也无法设计状态,因此考虑二分答案。

考虑固定一个 n,当 n 变大时,合法的序列个数不会变小,因为 n 较小时合法的序列,当 n 变大后依然是合法的。可以据此二分答案。

二分答案后,考虑枚举首相 k,然后枚举序列长度即可。同样由于 nO(m) 的,得到 k10154104。又因为序列长度是 O(logm) 的,因此求方案数的总复杂度是 O(logm10logm4)

因此总的时间复杂度是 O(10logm4log2m)

#include <cstdio>

typedef long long int ll;

ll m, ans;

ll calc(const ll n);

int main() {
  freopen("thief.in", "r", stdin);
  freopen("thief.out", "w", stdout);
  qr(m);
  for (ll l = 1, r = 1000000000000000000ll, mid = 1000000000ll; l <= r; mid = (l + r) >> 1) {
    if (calc(mid) >= m) {
      ans = mid; r = mid - 1;
    } else {
      l = mid + 1;
    }
  }
  qw(calc(ans) == m ? ans : -1, '\n', true);
  return 0;
}

ll calc(const ll n) {
  ll _ret = 0;
  for (int k = 2; ; ++k) {
    ll cnt = 0;
    for (ll i = n; i; i /= k) {
      if ((++cnt) >= 4) { _ret += i; }
    }
    if (cnt < 4) break;
  }
  return _ret;
}

C 盘子

Description

给定 n 个圆,第 i 个圆的半径为 ri,又有 m 个间距为 1 且排成一排的点,要求以这些点作为这 n 个圆的圆心,且圆之间两两不相交但允许相切,求方案数对大质数取模的结果。

Limitations

100% 3n4000, m109,ri105

60% 3n100,  m200

30% 2n5,  m30

Solution

30% 爆搜即可。时间复杂度 Cmn

对于 60% 的数据,考虑只考虑圆心所在的直线,则问题转化为了给定一堆线段,求互不相交的安放方案数。

考虑把一段长度为 1 的空白也看做一个元素,则问题转化为了有重复元素的全排列数,套公式即可。

注意到最两边的线段是可以有至多一半露在外面的,考虑枚举这两端的元素,然后枚举他们有多少露在外面,暴力算每种情况的方案数公式,注意到露在外面的每向里 1 的长度时,这种情况的方案数可以 O(1) 维护,这样就得到了一个 O(n3+n2r) 的做法,然后注意到两侧线段向里移动的过程中,每个数字在公式中产生的贡献是可以 O(1) 计算的,因此得到了一个 O(n3+n2×poly(log)) 的做法,但是没调出来。

posted @   一扶苏一  阅读(187)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2018-10-16 【极值问题】【CF1063B】 Labyrinth
点击右上角即可分享
微信分享提示