2022杭电多校(六)

2022杭电多校(六)

一、比赛小结

比赛链接:Problems (hdu.edu.cn)

二、题目分析及解法(基础题)

1006、Maex

题目链接:Problem - 7202 (hdu.edu.cn)

题意:

给出一棵有根树,每个节点的值为子节点的 m e x mex mex ,求根的最大值

题解:

转移为: d p [ u ] = s z [ u ] + max ⁡ ( d p [ v ] ) ,   v ∈ s u b t r e e ( u ) dp[u]=sz[u]+\max(dp[v]), \ v\in subtree(u) dp[u]=sz[u]+max(dp[v]), vsubtree(u) ,简单树形 dp

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 5e5 + 5;
int n;
// 父节点、深度、子树节点数
int pre[maxn], deep[maxn], num[maxn];
// 子树最长值、最长子节点
int len[maxn], son[maxn];
struct e {
  int to, next;
} edge[maxn << 1];
int head[maxn], cnt;
void init() {
  cnt = 1;
  memset(head, -1, sizeof(head));
  memset(deep, 0, sizeof(deep));
  memset(num, 0, sizeof(num));
  memset(len, 0, sizeof(len));
  memset(son, 0, sizeof(son));
  for (int i = 0; i < maxn; i++) edge[i].next = -1;
}
void addedge(int u, int v) {
  edge[cnt] = e{v, head[u]};
  head[u] = cnt++;
}
void predfs(int u, int fa, int d) {
  deep[u] = d, num[u] = 1, len[u] = 1, pre[u] = fa;
  for (int i = head[u]; i != -1; i = edge[i].next) {
    int v = edge[i].to;
    if (v == fa) continue;
    predfs(v, u, d + 1);
    num[u] += num[v];  // 子树的节点数
    len[u] = max(len[u], len[v] + 1);
    if (len[son[u]] < len[v]) son[u] = v;  // 长链子树的根节点
  }
}
signed main() {
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _t;
  cin >> _t;
  while (_t--) {
    init();
    cin >> n;
    for (int i = 1; i < n; i++) {
      int u, v;
      cin >> u >> v;
      addedge(u, v), addedge(v, u);
    }
    predfs(1, 0, 1);
    int ans = 1ll;
    for (int i = 1; i <= n; i++) {
      if (num[i] != 1) continue;
      int cur = i, tmp = 0ll;
      while (cur) {
        tmp += num[cur];
        cur = pre[cur];
        // cout << tmp << " " << cur << endl;
      }
      ans = max(ans, tmp);
    }
    cout << ans << endl;
  }
  return 0;
}

1007、Shinobu loves trip

题目链接:Problem - 7203 (hdu.edu.cn)

题意:

shinobu 有 n n n 个旅行计划,对应参数 s , d s, d s,d 意味着它会到达 s , s a , s a 2 , . . . , s a d − 1 s, sa, sa^2, ..., sa^{d-1} s,sa,sa2,...,sad1 ,有 q q q 次询问,给出 x i x_i xi ,问有多少个旅行计划可以到达 x i x_i xi 这个点。

题解:

s s s d d d 天之后会来到 ( s ∗ a d ) ( m o d P ) (s*a^d) \pmod P (sad)(modP) ,因此,判断一个点 x x x 是否会被一个计划 ( s , d ) (s, d) (s,d) 经过,只需要判断是否存在一个 0 ≤ k ≤ d 0\leq k \leq d 0kd 使得 ( s ∗ a d ) ( m o d P ) = x (s*a^d) \pmod P = x (sad)(modP)=x ,由于 P P P 是质数,所以已知 x x x s s s 的值,可以得到 a k a^k ak 的值。

预处理所有 a 0 , a 1 , . . . , a 200000 a^0, a^1, ..., a^{200000} a0,a1,...,a200000 ,然后直接查询算出来的 a k a^k ak 是否在预处理出的数里面,如果不在,这个计划一定不经过 x x x ,否则可以得到 k k k 的具体值,判断 k ≤ d k\leq d kd 即可。要注意特判 s = 0 s=0 s=0 的情况。

n log ⁡ P n\log P nlogP 预处理。对于每个查询,遍历所有计划得到答案。

代码:

#include<bits/stdc++.h>
#define ll long long
#define all(x) x.begin(),x.end()
using namespace std;
int qm(int a, int b, const int P){
    int res = 1;
    while(b){
        if(b&1) res = (ll)res*a%P;
        a = (ll)a*a%P;
        b >>= 1;
    }return res;
}
ll P; int a;
int n, q;
const int maxn = 2e5 + 5;
void sol1(){
    unordered_map<int, int> f;
    ll cur = 1; int len = 0;
    while(len<maxn && !f.count(cur)){
        f[cur] = len;
        cur = (ll)cur*a%P;
        len++;
    }
    vector<int> s(n), is(n);
    vector<int> d(n);
    for(int i = 0; i < n; ++i){
        cin>>s[i]>>d[i];
        assert(s[i] < P && s[i] >= 0);
        assert(d[i] <= 200000 && d[i] >= 1);
        if(s[i]) is[i] = qm(s[i], P-2, P);
    }
    while(q--){
        ll x; cin>>x;
        assert(x >= 0 && x < P);
        int ans = 0;
        if(x == 0){
            for(int i = 0; i < n; ++i) if(s[i]==0) ans++;
        }else{
            for(int i = 0; i < n; ++i){
                if(s[i] == 0) continue;
                ll ak = (ll)x * is[i] % P;
                if(f.count(ak) && f[ak] <= d[i]) ans++;
            }
        }
        cout<<ans<<endl;
    }
}
int main(){
    int T = 1; cin>>T; assert(T <= 5 && T > 0);
    while(T--){
        cin>>P>>a;
        assert(P > 2 && P <= 1e9+7);
        assert(a >= 2 && a < P);
        cin>>n>>q;
        assert(n <= 1000 && n > 0);
        assert(q <= 1000 && q > 0);
        
        sol1();
    }
    return 0;
}

1008、Shinobu Loves Segment Tree

题目链接:Problem - 7204 (hdu.edu.cn)

题意:

有一棵线段树,每个节点记录子节点的值的加和,每次 shinobu 会 u p d a t e ( 1 , 1 , i , 1 ) update(1, 1, i, 1) update(1,1,i,1) ,他会 u p d a t e update update n n n 天,然后问 x x x 节点的值为何

题解:

考虑第 a a a 天,会对 v a l u e [ x ] value[x] value[x] 产生的影响:

依次看 x x x 次高位到最低位:

如果这一位是1,则说明 x 在右子树,当前区间长度 a = ⌊ a 2 ⌋ a=\lfloor \frac{a}2 \rfloor a=2a

如果这一位是0,则说明 x 在左子树,当前区间长度 a = ⌊ a 2 ⌋ a=\lfloor \frac{a}2 \rfloor a=2a

由此可以在 O ( log ⁡ a ) O(\log a) O(loga) 时间快速模拟得到某一天 v a l u e [ x ] value[x] value[x] 的变化。

现在考虑 c a l c ( l , r , p ) calc(l, r, p) calc(l,r,p) 表示,从第 l l l 天到第 r r r 天,上述模拟过程从 x x x 的第 p p p 位进行到第 0 0 0 位,对 v a l u e [ x ] value[x] value[x] 产生的贡献。

那么可以发现, c a l c ( l , r , p ) calc(l, r, p) calc(l,r,p) 可以由 c a l c ( l / 2 , r / 2 , p − 1 ) calc(l/2, r/2, p-1) calc(l/2,r/2,p1) 或者 c a l c ( ( l + 1 ) / 2 , ( r + 1 ) / 2 , p − 1 ) calc((l+1)/2, (r+1)/2, p-1) calc((l+1)/2,(r+1)/2,p1) 推得 。

具体的,对任意 k > 0 k>0 k>0 , 第 2 k 2k 2k 天和第 2 k + 1 2k+1 2k+1 天,下取整之后,他们对应的区间长度相同,后续的操作也相同,因此对答案产生的贡献也相同。上取整同理可得类似关系。总复杂度 T log ⁡ 2 n T\log^2n Tlog2n

代码:

#include <bits/stdc++.h>
using namespace std;
int t, n;
long long x;
vector<int> d;
long long get(int x, int n) {
  if (x == d.size()) return n;
  if (n < 2) return 0;
  return d[x] ? get(x + 1, n / 2) : get(x + 1, (n + 1) / 2);
}
long long dg(int x, int l, int r) {
  if (x == d.size()) return 1ll * (l + r) * (r - l + 1) / 2;
  if (l == 1) l++;
  if (l > r) return 0;
  if (d[x]) {
    long long ans = dg(x + 1, l / 2, r / 2) * 2;
    if (l % 2 == 1) ans -= get(x + 1, l / 2);
    if (r % 2 == 0) ans -= get(x + 1, r / 2);
    return ans;
  } else {
    long long ans = dg(x + 1, (l + 1) / 2, (r + 1) / 2) * 2;
    if (l % 2 == 0) ans -= get(x + 1, (l + 1) / 2);
    if (r % 2 == 1) ans -= get(x + 1, (r + 1) / 2);
    return ans;
  }
}
int main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  cin >> t;
  while (t--) {
    cin >> n >> x;
    d.clear();
    while (x) d.emplace_back(x & 1), x >>= 1;
    d.pop_back();
    reverse(d.begin(), d.end());
    cout << dg(0, 1, n) << '\n';
  }
  return 0;
}

1009、Map

题目链接:Problem - 7205 (hdu.edu.cn)

题意:

给出两个地图,求其不动点

题解:

解方程即可,比赛时在想阿氏圆,但最终还是没搞出来

本题的题目背景是著名的巴拿赫不动点定理. 由于大地图到小地图的映射关系是一个压缩映射, 因此存在唯一不动点. 求这个不动点的方式有很多.

设所求的不动点为点 P P P . 由于 A B ⊥ A D AB \perp AD ABAD , 因此可设: O P → = O A → + λ A B → + μ A D → \overrightarrow{OP}=\overrightarrow{OA}+\lambda \overrightarrow{AB}+\mu \overrightarrow{AD} OP =OA +λAB +μAD 其中 λ , μ \lambda ,\mu λ,μ 为待定参数. 由于 P P P 在两个地图上的对应关系相同, 因此有: O P → = O a → + λ a b → + μ a d → \overrightarrow{OP} = \overrightarrow{Oa} + \lambda \overrightarrow{ab} +\mu \overrightarrow{ad} OP =Oa +λab +μad 联立消去 O P → \overrightarrow{OP} OP 得: λ ( A B → − a b → ) + μ ( A D → − a d → ) = O a → − O A → \lambda (\overrightarrow{AB} - \overrightarrow{ab}) + \mu (\overrightarrow{AD} - \overrightarrow{ad}) = \overrightarrow{Oa} - \overrightarrow{OA} λ(AB ab )+μ(AD ad )=Oa OA

分别考虑 x x x 分量和 y y y 分量可以得到一个关于 λ , μ \lambda , \mu λ,μ 的二元一次方程组. 根据巴拿赫不动点定理, 方程有唯一解. 求出 λ \lambda λ 后即可得到 P P P 的坐标.

代码:

#include <bits/stdc++.h>
using namespace std;
struct point {
  double x, y;
  point(double x = 0.0, double y = 0.0) : x(x), y(y) {}
  point operator+(const point &p) const { return point(x + p.x, y + p.y); }
  point operator-(const point &p) const { return point(x - p.x, y - p.y); }
  point operator*(const double &d) const { return point(x * d, y * d); }
};
point A, B, C, D;
point a, b, c, d;
point p;
int main() {
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
  int _t;
  scanf("%d", &_t);
  while (_t--) {
    scanf("%lf%lf%lf%lf%lf%lf%lf%lf", &A.x, &A.y, &B.x, &B.y, &C.x, &C.y, &D.x,
          &D.y);
    scanf("%lf%lf%lf%lf%lf%lf%lf%lf", &a.x, &a.y, &b.x, &b.y, &c.x, &c.y, &d.x,
          &d.y);
    point p1 = ((B - A) - (b - a)), p2 = ((D - A) - (d - a)), p3 = (a - A);
    double lambda, mu;
    lambda = (p3.x * p2.y - p3.y * p2.x) / (p1.x * p2.y - p1.y * p2.x);
    mu = (p3.x * p1.y - p3.y * p1.x) / (p2.x * p1.y - p1.x * p2.y);
    p = A + (B - A) * lambda + (D - A) * mu;
    printf("%.10lf %.10lf\n", p.x, p.y);
  }
  return 0;
}

1010、Planar graph

题目链接:Problem - 7206 (hdu.edu.cn)

题意:

给出一个平面图,求其可划分出的区域,在不同区域间建立 “隧道” ,求可以使得所有区域均连通的最小隧道数

题解:

其实用到了很多拓扑的东西,最后的隧道数其实是固定的,首先进行删边,将那些不改变连通性的边删去,然后在剩下的边里按序从大到小枚举,一边选取,一边删“无用”边,这样即可得到最终答案。

官方题解:

本题的题目背景是平面图欧拉定理的一种证明方式. 根据平面图欧拉定理 V − E + F = k + 1 V-E+F=k+1 VE+F=k+1 ( V , E , F , k V, E, F, k V,E,F,k 分别为点、边、面、连通分量的个数 ), 每去掉一条边 ( 加一个隧道可以理解为删除一条边 ) , 平面图 G G G 的面的个数就会增加 1 . 因此最终边的个数为 V + 1 − k − 1 = V − k V+1-k-1=V-k V+1k1=Vk , 因此删边个数为 E − V + k E-V+k EV+k . 于是对于每个连通分量, 保留了 V ′ − 1 V'-1 V1 条边( V ′ V' V 为该连通分量的点数). 如果该连通分量有圈, 那么这个圈内的区域一定不与外部的无穷平面连通, 不符合题意. 因此每个连通分量都不含圈, 且有 V ′ − 1 V'-1 V1 条边, 因此每个连通分量都变成树.

现在从另外一个角度考虑. 我们构造一个新图 G ˉ \=G Gˉ . 我们把平面图的每个面当作一个点, 如果两个面被平面图的某条边分隔, 那么这两个面在 G ˉ \=G Gˉ 对应的点之间连一条边(于是 G G G G ˉ \=G Gˉ 的边是一一对应关系). G ˉ \=G Gˉ 的点个数为 F = k + 1 − V + E F=k+1-V+E F=k+1V+E , G G G 的删边个数为 E − V + k = F − 1 E-V+k=F-1 EV+k=F1 , 由于建隧道之后任意两个区域可达, 因此如果把 G G G 中删掉的边在 G ˉ \=G Gˉ 中考虑, 删除 G ˉ \=G Gˉ 中在 G G G 保留的边, 那么边个数为 F − 1 F-1 F1 , 且任意两点连通. 因此这些边构成 G ˉ \=G Gˉ 的生成树.

于是, 问题转化为求 G ˉ \=G Gˉ 的字典序最小的生成树. 由于每条边边权不同, 因此字典序最小的生成树等价于 G ˉ \=G Gˉ 最小生成树, 等价于 G ˉ \=G Gˉ 的生成树边权和最小(考虑 K r u s k a l Kruskal Kruskal 算法的构造过程, 并注意所有边权不同时最小生成树唯一). 由于所有边边权和为定值, 因此 G ˉ \=G Gˉ 生成树边权和最小等价于 G G G 的每个连通分量对应的树边权和最大. 于是只需要对于 的每个连通分量求出最大生成树, 然后这些最大生成树没有用到的边就是 G ˉ \=G Gˉ 的最小生成树的边.

代码:

#include <bits/stdc++.h>
using namespace std;

const int _ = 1e5 + 5;
const int __ = 2e5 + 5;

int n, m;

int p[_];
bool vis[__];
int u[__], v[__];

int dsu(int x) { return p[x] == x ? x : p[x] = dsu(p[x]); }

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int T;
  cin >> T;
  while (T--) {
    memset(vis, 0, sizeof vis);
    memset(p, 0, sizeof p);
    memset(u, 0, sizeof u);
    memset(v, 0, sizeof v);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
      p[i] = i;
    }
    for (int i = 1; i <= m; i++) {
      cin >> u[i] >> v[i];
    }
    int tot = m;
    for (int i = m; i >= 1; i--) {
      int x = dsu(u[i]), y = dsu(v[i]);
      if (x != y) {
        tot--;
        vis[i] = 1;
        p[x] = y;
      }
    }
    cout << tot << endl;
    for (int i = 1; i <= m; i++) {
      if (!vis[i]) cout << i << ' ';
    }
    cout << endl;
  }
  return 0;
}

1012、Loop

题目链接:Problem - 7208 (hdu.edu.cn)

题意:

给定一个序列,每次操作可以选定一个区间,可以让区间首位元素移到最后,其余元素向前移动一位,要求严格使用 k k k 次操作,求可以得到的字典序最小的序列

题解:

那么显然可以贪心地求解,我们会尽量让较小的向前移,具体地,在序列中找到第一个 a i − 1 > a i a_{i-1}>a_i ai1>ai 的位置,然后将 a i a_i ai 移至前面即可

代码:

#include <bits/stdc++.h>
using namespace std;

const int M = 3e5 + 9;
int n, k, num;
int a[M], s[M], b[M];
void work() {
  int top = 0, num = 0;
  scanf("%d%d", &n, &k);
  for (int i = 1; i <= n; ++i) {
    scanf("%d", &a[i]);
  }
  priority_queue<int> q;
  for (int i = 1; i <= n; ++i) {
    while (top && s[top] < a[i] && k) {
      q.push(s[top]);
      top--;
      k--;
    }
    s[++top] = a[i];
  }
  for (int i = 1, l = 1; i <= n; ++i) {
    if (l > top) {
      b[++num] = q.top();
      q.pop();
    } else if (q.empty()) {
      b[++num] = s[l];
      l++;
    } else {
      if (q.top() > s[l]) {
        b[++num] = q.top();
        q.pop();
      } else {
        b[++num] = s[l];
        l++;
      }
    }
  }
  for (int i = 1; i <= n; ++i) {
    printf("%d%c", b[i], " \n"[i == n]);
  }
}
int main() {
  freopen("1.in", "r", stdin);
  freopen("1.out", "w", stdout);
  int T;
  scanf("%d", &T);
  while (T--) work();
  return 0;
}

三、题目分析及解法(进阶题)

不会做X

1001、

1002、

1003、

1004、

1005、

1011、

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值