Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2)

写在前面

比赛地址:https://codeforces.com/contest/1994

依旧是红温温温温温温温温温温场,但这场大概掉不了分了呃呃呃呃

鉴定为写数据结构写啥了。

A

签到。

仅有 \(1\times 1\) 无解,其他情况保证所有数不同,随便做就行。

我的做法是选择大于 2 的一维做一个循环移位。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 20;
//=============================================================4
int n, m, a[kN][kN], b[kN][kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m;
    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= m; ++ j) {
        std::cin >> a[i][j];
      }
    }
    if (n == 1 && m == 1) std::cout << -1 << "\n";
    else {
      if (n > 1) {
        for (int i = 1; i <= n; ++ i) {
          for (int j = 1; j <= m; ++ j) {
            if (i > 1) b[i][j] = a[i - 1][j];
            else b[i][j] = a[n][j];
          }
        }
      } else {
        for (int i = 1; i <= n; ++ i) {
          for (int j = 1; j <= m; ++ j) {
            if (j > 1) b[i][j] = a[i][j - 1];
            else b[i][j] = a[i][m];
          }
        }
      }
      for (int i = 1;i <= n; ++ i) {
      for (int j = 1; j <= m; ++ j) {
        std::cout << b[i][j] << " ";
      }
      std::cout << "\n";
    }
    }
  }
  return 0;
}

B

结论,手玩。

\(s\) 中第一个 1 位于 \(p\),发现最优的操作是每次操作均选择长度为 \(p\) 的区间 \([i, i + p - 1]\),从而仅将位于 \(i + p - 1\) 的位翻转,且其他任意操作都可以被若干次该操作替换。

\(s\) 的第 \(p\) 位及之后的所有位都可以被任意翻转,则仅需判断 \(s\) 的前缀 \(s[0, p - 1]\)\(t[0, p - 1]\) 是否相同即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n;
std::string s, t;
//=============================================================
bool check1() {
  for (int i = 0; i < n; ++ i) {
    if (s[i] == '1') return true;
  }
  return false;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    std::cin >> s >> t;
    if (s == t) {
      std::cout << "YES\n";
    } else {
      int flag = 1;
      for (int i = 0; i < n; ++ i) {
        if (s[i] == '1') {
          break;
        } else if (t[i] == '1') {
          flag = 0;
          break;
        } 
      }
      if (flag) std::cout << "YES\n";
      else std::cout << "NO\n";
    }
  }
  return 0;
}

C

递推,二分。

一个显然的想法是枚举选择的区间的端点。

考虑枚举左端点 \(l\),二分求得令 \(\sum_{l\le i\le R} a_i \le x\) 的最近的 \(R\),则显然所有 \(r\in [l, R]\) 均是有贡献的。又连续吃完 \([l, R+1]\)\(g\) 重新归零,则仅需再直接加上左端点为 \(R + 2\) 的贡献即可。

总时间复杂度 \(O(n\log n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n;
LL ans, x, a[kN], pre[kN], f[kN];
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> x;
    ans = 0;

    for (int i = 1; i <= n; ++ i) {
      std::cin >> a[i];
      pre[i] = pre[i - 1] + a[i];
    }
    for (int i = 1; i <= n + 2; ++ i) f[i] = 0;
    for (int i = n; i; -- i) {
      int l = i, r = n, p = i - 1;
      while (l <= r) {
        int mid = (l + r) / 2;
        if (pre[mid] - pre[i - 1] <= x) {
          p = mid;
          l = mid + 1;
        } else {
          r = mid - 1;
        }
      }
      f[i] = (p - i + 1) + f[p + 2];
      ans += f[i];
    }
    std::cout << ans << "\n";
  }
  
  return 0;
}

D

结论,暴力。

妈的写数据结构写傻了我满脑子都是怎么尺取法一下搞一个合法的点集区间

发现若数列中有重复元素 \(a_i = a_j\),则边 \((i, j)\) 可以在第任意次操作中连上,相当于缩小了问题规模,于是仅需考虑 \(a_i\) 两两不同的情况。手玩下发现此时在模 \(n-1\) 剩余系下,一定存在两个数之差为 \(0\),即 \(n\) 个两两不同的数,两者之差中一定有 \(n-1\) 的倍数,考虑从其中任选一对儿连边,于是问题缩小为 \(n - 1\) 个两两不同的数,由上述结论再归纳可知对于任意数列一定有解。

那么就很好实现了,仅需对于所有模数(也即第几次操作) \(p\in[1, n - 1]\),记录余数为 \(0\sim p - 1\) 的数的位置集合,然后倒序枚举操作,每次任选一对儿在模 \(p\) 下余数相同的且不连通的点连边即可。

要是没发现上面归纳得到的结论直接大力枚举冲就直接过了呃呃呃呃,还等到证出来再冲亏麻了这么水的 E 也没看我草

总时间复杂度 \(O(n^2)\) 级别。

Update on 2024.8.1:下面这份代码枚举的方法太烂了被赛后新增的数据卡了、、、建议参考官方题解:https://codeforces.com/blog/entry/131666

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 2010;
//=============================================================
int n, fa[kN], a[kN]; 
std::vector<pii> ans;
std::vector<int> node[kN][kN];
bool vis[kN];
//=============================================================
int find(int x_) {
  return (fa[x_] == x_) ? (x_) : (fa[x_] = find(fa[x_]));
}
void merge(int x_, int y_) {
  int fx = find(x_), fy = find(y_);
  if (fx == fy) return;
  fa[fx] = fy;
}
void solve() {
  for (int i = 1; i <= n; ++ i) {
    for (int j = 0; j < n; ++ j) {
      node[i][j].clear();
    }
  }
  
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j < n; ++ j) {
      node[j][a[i] % j].push_back(i);
    }
  }

  for (int d = n - 1; d; -- d) {
    int flag = 0;
    for (int mod = 0; mod < d; ++ mod) {
      if (flag) break;
      if (node[d][mod].size() >= 2) {
        for (auto x: node[d][mod]) {
          if (flag) break;
          for (auto y: node[d][mod]) {
            if (x == y) continue;
            if (find(x) == find(y)) continue;
            merge(x, y);
            ans.push_back(mp(x, y));
            flag = 1;
            break;
          }
        }
      }
    }
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    ans.clear();

    for (int i = 1; i <= n; ++ i) {
      std::cin >> a[i];
      fa[i] = i;
    }
    solve();

    std::cout << "YES\n";
    std::reverse(ans.begin(), ans.end());
    for (auto x: ans) 
      std::cout << x.first << " " << x.second << "\n";
  }
  return 0;
}

E

二进制,贪心。

实际和树完全没关系的诈骗!比 D 简单一万倍!

发现对于一棵大小为 \(sz\) 的树可以随便删叶子,将删去整棵树的贡献调整为任意 \(v\in [1, sz]\);又是按位或操作,发现对于一棵树若将其拆成多部分进行贡献,均可以转化成先删叶子再把整棵树全部删掉的形式,即树的形态是完全不重要的,仅需考虑 \(sz\) 即可。

考虑当前枚举到第 \(i\) 棵树,使用前 \(i - 1\) 棵树使得答案最大变成了 \(s\)。考虑拆位,从高位向低位检查第 \(i\) 棵树的贡献:

  • \(sz\) 该位为 0 则跳过。
  • \(sz\) 该位为 1,\(s\) 该位为 0,则应当保留该位的贡献使得 \(s\) 中该位变为 1,即不断删叶子后该位及更高位应当保留。
  • \(sz\) 该位为 1,\(s\) 该位为 1,则该位的贡献已经获得,此时一种最优的操作方案是不断删叶子,直至刚好删去该位将 \(sz\) 的后面所有位均变为 1,从而使 \(s\) 中该位之后的所有位均变为 1。此时即可结束检查。

总时间复杂度 \(O(k\log n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int k, sz[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> k;
    for (int i = 1; i <= k; ++ i) {
      std::cin >> sz[i];
      for (int j = 1, x; j < sz[i]; ++ j) std::cin >> x;
    }
    std::sort(sz + 1, sz + k + 1);
    
    int ans = 0;
    for (int i = k; i; -- i) {
      for (int j = 21; j >= 0; -- j) {
        int x = ans >> j & 1, y = sz[i] >> j & 1, pow2 = 1 << j;
        if (y == 0) continue;
        if (x == 0) {
          ans |= pow2;
        } else {
          ans |= pow2 - 1;
          break;
        }
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

F

写在最后

学到了什么:

  • D:大胆冲!
  • E:二进制贪心;诈骗!

现在是 2024 年 7 月 19 日 01:50:12,我现在要睡到五点半起床然后闪击长沙蔚蓝档案罗森主题店。

实际sensei!

update on 10:33:

闪击结束!

https://images.cnblogs.com/cnblogs_com/luckyblock/2359288/o_240719103237_da979b457654b748a7c9a72f89b3d9f9.jpg

https://images.cnblogs.com/cnblogs_com/luckyblock/2359288/o_240719103319_f99cc070d9d3a0042edb97e6033d09da.jpg

posted @ 2024-07-19 02:05  Luckyblock  阅读(711)  评论(6编辑  收藏  举报