2022杭电多校(八)

2022杭电多校(八)

一、比赛小结

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

这场签到题挺多

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

1001、Theramore

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

题意:

一个 01 序列,可以任意翻转奇数长度的区间,求能达到的最小字典序。

题解:

考虑操作不变性,位置的奇偶性不变,按奇偶分类即可

代码:

// 1001, std
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;

char s[MAXN];
int n, cnt[2][2];

void solve() {
  scanf("%s", s + 1);
  n = strlen(s + 1);
  cnt[0][0] = cnt[0][1] = cnt[1][0] = cnt[1][1] = 0;
  for (int i = 1; i <= n; ++i) {
    ++cnt[i & 1][s[i] == '1'];
  }
  for (int i = 1; i <= n; ++i) {
    if (cnt[i & 1][0]) {
      putchar('0');
      --cnt[i & 1][0];
    } else {
      putchar('1');
    }
  }
  puts("");
}

int main() {
  int T;
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

1004、Quel’Thalas

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

题意:

二维平面上, [ 0 , n ] ∗ [ 0 , n ] [0, n]*[0, n] [0,n][0,n] 的整点中去掉 ( 0 , 0 ) (0, 0) (0,0) 之后最少需要多少条直线使得每个点至少有一条边覆盖。

题解:

a n s = 2 ∗ n ans = 2*n ans=2n

我们考虑如果不切 x = n x=n x=n y = n y=n y=n , 那么同一条直线最多穿越两个边界上的点,总共有 4 n 4n 4n 个边界上的点,所以至少要 2 n 2n 2n 条直线。

代码:

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

int main() {
  //	freopen("1004.in", "r", stdin);
  //	freopen("1004.out", "w", stdout);
  int T, n;
  for (cin >> T; T--;) {
    scanf("%d", &n);
    printf("%d\n", n * 2);
  }
}

1005、Ironforge

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

题意:

一条链,每个点上有一个数 a a a ,每条边上有一个质数 b i b_i bi 。一开始在某个点上,有一个空背包,走到一个点上可以把它的质因子放进背包,一条边如果背包里有那个质数就可以走。多组询问求从 x 出发能否走到 y(即求每个点能走到的最大范围)。

题解:

求出从每个点出发能到达的最大的范围,即可 O ( 1 ) O(1) O(1) 回答所有询问。

先从大到小枚举,预处理每个点只向右走的最大范围,如果能向右走一步就先取 i+1 的范围,然后沿着之前预处理的范围向后跳。向后跳的次数是均摊 O ( 1 ) O(1) O(1) 的。

然后从小到大枚举,求每个点能走到的最大范围。

如果 i i i 能走到 i + 1 i+1 i+1 :如果 i + 1 i+1 i+1 已经预处理的向右的范围无法让它走回 i i i 则取预处理的答案;否则 i + 1 i+1 i+1 i i i 范围相同。

如果 i i i 不能走到 i + 1 i+1 i+1 :先取预处理的范围,然后向左向右扩展,都沿着已知的范围暴力跳即可。向左向右跳的次数都是均摊 O ( 1 ) O(1) O(1) 的。

判断一个范围内是否存在某个质因子,方法有很多,标程采用的是对每个质因子维护一个 vector 存它出现的所有位置,在 vector 上二分。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码:

// 1005, std
#include <bits/stdc++.h>
#define mset(a, b) memset(a, b, sizeof(a))
#define mcpy(a, b) memcpy(a, b, sizeof(a))
using namespace std;
typedef long long LL;
const int MAXN = 200005;

template <typename T>
inline void read(T &WOW) {
  T x = 0, flag = 1;
  char ch = getchar();
  while (!isdigit(ch)) {
    if (ch == '-') flag = -1;
    ch = getchar();
  }
  while (isdigit(ch)) {
    x = x * 10 + ch - '0';
    ch = getchar();
  }
  WOW = flag * x;
}

int n, m, a[MAXN], b[MAXN], clean[MAXN], tim;
int L[MAXN], R[MAXN];

int pr[MAXN], pcnt, vis[MAXN], low[MAXN];
vector<int> pos[MAXN];

bool Check(int p, int l, int r) {
  if (!pos[p].size() || pos[p].back() < l) return 0;
  int x = *lower_bound(pos[p].begin(), pos[p].end(), l);
  return (x <= r);
}

void sieve() {
  n = 200000;
  for (int i = 2; i <= n; ++i) {
    if (!vis[i]) {
      pr[++pcnt] = i;
      low[i] = i;
    }
    for (int j = 1; j <= pcnt && i * pr[j] <= n; ++j) {
      vis[i * pr[j]] = 1;
      low[i * pr[j]] = pr[j];
      if (i % pr[j] == 0) break;
    }
  }
}

void solve() {
  read(n);
  read(m);
  for (int i = 1; i <= n; ++i) {
    read(a[i]);
  }
  for (int i = 1; i < n; ++i) {
    read(b[i]);
  }
  for (int i = 1; i <= n; ++i) {
    int x = a[i];
    while (x > 1) {
      int nowp = low[x];
      while (nowp == low[x]) {
        x /= nowp;
      }
      if (clean[nowp] != tim) {
        pos[nowp].clear();
        clean[nowp] = tim;
      }
      pos[nowp].push_back(i);
    }
  }
  ++tim;

  for (int i = n; i >= 1; --i) {
    R[i] = i;
    while (R[i] < n && Check(b[R[i]], i, R[i])) {
      R[i] = R[R[i] + 1];
    }
  }
  for (int i = 1; i <= n; ++i) {
    if (i > 1 && R[i - 1] >= i) {
      if (Check(b[i - 1], i, R[i])) {
        L[i] = L[i - 1];
        R[i] = R[i - 1];
      } else {
        L[i] = i;
      }
    } else {
      L[i] = i;
      while (1) {
        bool flag = 0;
        while (R[i] < n && Check(b[R[i]], L[i], R[i])) {
          flag = 1;
          R[i] = R[R[i] + 1];
        }
        while (L[i] > 1 && Check(b[L[i] - 1], L[i], R[i])) {
          flag = 1;
          L[i] = L[L[i] - 1];
        }
        if (!flag) break;
      }
    }
    // cerr << L[i] << ' ' << R[i] << endl;
  }

  for (int i = 1, x, y; i <= m; ++i) {
    read(x);
    read(y);
    if (L[x] <= y && y <= R[x]) {
      puts("Yes");
    } else {
      puts("No");
    }
  }
}

int main() {
  sieve();
  int T;
  read(T);
  while (T--) {
    solve();
  }
  return 0;
}

1007、Darnassus

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

题意:

给出一个排列 p p p ,把每个位置视为点,建一个无向图, i , j i, j i,j 之间的边权为 ∣ i − j ∣ ∗ ∣ p i − p j ∣ |i-j|*|p_i-p_j| ijpipj 。求这个图的最小生成树。

题解:

依次连接 i , i + 1   ( 1 ≤ i ≤ n ) i, i+1 \ (1\leq i \leq n) i,i+1 (1in) ,这样的生成树每条边边权都 ≤ n − 1 \leq n-1 n1 ,因此存在一种最小生成树中也只有边权 n − 1 n-1 n1 的边(Kruskal 算法的原理)。 ∣ i − j ∣ ∗ ∣ p i − p j ∣ ≤ n − 1 |i-j|*|p_i-p_j| \leq n-1 ijpipjn1 意味着 ∣ i − j ∣ |i-j| ij ∣ p i − p j ∣ |p_i-p_j| pipj 必有至少一个 ≤ n − 1 \leq \sqrt{n-1} n1 ,因此可以在 O ( n n ) O(n\sqrt{n}) O(nn ) 的时间复杂度内找到所有这样的边,然后使用 Kruskal 算法求出最小生成树即可。

总时间复杂度 O ( n n α ( n ) ) O(n\sqrt{n} \alpha(n)) O(nn α(n))

代码:

#include <bits/stdc++.h>
#define ll long long
// #define int long long
using namespace std;
const int maxn = 5e4 + 5;
const int maxm = 1e8 + 5;
int n, sqrtn, m;
ll ans;
int p[maxn], q[maxn];
int pre[maxn];
struct e {
  int u, v;
  int next;
} edge[maxm];
int head[maxn];
void addedge(int u, int v, int w) {
  edge[m].u = u, edge[m].v = v;
  edge[m].next = head[w];
  head[w] = m++;
}
void init() {
  sqrtn = sqrt(n);
  m = ans = 0;
  memset(head, -1, sizeof(head));
  for (int i = 0; i < maxn; i++) pre[i] = i;
}
int find(int x) {
  if (pre[x] == x) return x;
  return pre[x] = find(pre[x]);
}
void kruskal() {
  int cnt = 0;
  for (int i = 1; i < n; i++) {
    for (int j = head[i]; j != -1; j = edge[j].next) {
      int u = find(edge[j].u), v = find(edge[j].v);
      if (u == v) continue;
      pre[u] = v;
      cnt++;
      ans += i;
      if (cnt == n - 1) return;
    }
  }
}
signed main() {
  clock_t startTime = clock();
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _;
  cin >> _;
  while (_--) {
    cin >> n;
    init();
    for (int i = 1; i <= n; i++) cin >> p[i], q[p[i]] = i;
    for (int len = 1; len <= sqrtn; len++) {
      for (int i = 1; i + len <= n; i++) {
        int tmp = abs(len * (p[i] - p[i + len]));
        if (tmp < n) addedge(i, i + len, tmp);
        tmp = abs(len * (q[i] - q[i + len]));
        if (tmp < n) addedge(q[i], q[i + len], tmp);
      }
    }
    kruskal();
    cout << ans << endl;
  }
  clock_t endTime = clock();
  // cout << "time: " << double(endTime - startTime) / CLOCKS_PER_SEC << endl;
  return 0;
}

1008、Orgrimmar

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

题意:

求一棵树的最大分离集的大小

题解:

d p [ x ] [ 0 ] , d p [ x ] [ 1 ] , d p [ x ] [ 2 ] dp[x][0], dp[x][1], dp[x][2] dp[x][0],dp[x][1],dp[x][2] 分别表示仅考虑 x 这个子树,x 号点未选,x号点选择了但是度数为0,x 号点选了且度数为 1 的选且 x 子树内部满足

分离集定义选择点数的最大值。直接dp即可!

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int n;
struct e {
  int to, next;
} edge[maxn << 1];
int cnt, head[maxn << 1];
int dp[maxn][3];
void init() {
  cnt = 0;
  memset(head, -1, sizeof(head));
}
void addedge(int u, int v) {
  edge[cnt].to = v;
  edge[cnt].next = head[u];
  head[u] = cnt++;
}
void dfs(int u, int fa) {
  dp[u][0] = 0;  // 不匹配
  dp[u][1] = 1;  // 主动匹配
  dp[u][2] = 1;  // 被动匹配
  int tmp = 0;
  for (int i = head[u]; i != -1; i = edge[i].next) {
    int v = edge[i].to;
    if (v == fa) continue;
    dfs(v, u);
    tmp += dp[v][0];
    dp[u][0] += max(dp[v][0], max(dp[v][1], dp[v][2]));
    dp[u][1] = max(dp[u][1], 1 + dp[v][2] - dp[v][0]);
    dp[u][2] += dp[v][0];
  }
  dp[u][1] += tmp;
}
int main() {
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
  int size(512 << 20);  // 512M
  __asm__("movq %0, %%rsp\n" ::"r"((char*)malloc(size) + size));
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  int _;
  cin >> _;
  while (_--) {
    init();
    cin >> n;
    for (int i = 1; i < n; i++) {
      int u, v;
      cin >> u >> v;
      addedge(u, v), addedge(v, u);
    }
    dfs(1, 0);
    int ans = 0;
    ans = max(ans, dp[1][0]);
    ans = max(ans, dp[1][1]);
    ans = max(ans, dp[1][2]);
    // for (int i = 1; i <= n; i++)
    //   cout << dp[i][0] << " " << dp[i][1] << " " << dp[i][2] << endl;
    cout << ans << endl;
  }
  exit(0);
}

1011、Stormwind

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

题意:

一个 n ∗ m n*m nm 的长方形,可以沿水平或竖直方向画若干条线,每条线的两端点都在长方形的边界上。要求这些线划分出的每个小长方形面积都 ≥ k \geq k k ,求最多可以画几条线。

题解:

枚举小长方形的某个边长的最小值,由此可以求出另一个边长允许的最小值,然后求出两个方向分别最多能画几条线。时间复杂度 O ( k ) O(k) O(k)

代码:

#include <bits/stdc++.h>
using namespace std;
int n, m, k;
int main() {
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int _;
  cin >> _;
  while (_--) {
    cin >> n >> m >> k;
    int ans = 0;
    if (n < m) swap(n, m);
    for (int b = 0; b < m; b++) {
      int u = m / (b + 1);
      int v = (k - 1) / u + 1;
      int a = n / v;
      ans = max(ans, a + b);
    }
    cout << ans << endl;
  }
  return 0;
}

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

不会做X

1002、

1003、

1006、

1009、

1010、

1012、

1013、

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值