排队

枚举 数学

思路

首先考虑如何支持等差,不难想到枚举差 然后在此方差的情况下选择修改次数最小的方案。由于它规定了值域是 ,而方差为 时目标 至少为 ,所以枚举上界就是 。这个枚举时间复杂度

对于第 个元素,我们生成基准目标元素 ,那么需要将原始 修改成和基准目标元素每个差值相同的(保证插值 )。具体来说,选择与基准元素差值相同数量最多的,最后需要修改的数量就越少。

这个检查是 线性的。但是由于需要用到负数下标,我使用了 map

注意

需要特别注意第一个元素和最后一个元素,不能超过

Hack 数据 ,有

对于

答案是 不是 ,因为最后一个元素无法别改成

时间复杂度

const LL kMaxN = 3e5 + 5;
 
LL a[kMaxN], n, m, ans = 1e18;
map<LL, LL> f;
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (LL i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (LL k = 0; 1 + (n - 1) * k <= m; k++) {
    f.clear();
    for (LL i = 1; i <= n; i++) {
      int d = 1 + (i - 1) * k - a[i];
      if (1 - d >= 1 && 1 + (n - 1) * k - d <= m) {
        f[d]++;
      }
    }
    for (auto i : f) {
      ans = min(ans, n - i.second);
    }
  } 
  cout << ans << '\n';
  return 0;
}

攀比

结论 找规律

思路

这道题目有多个询问,这代表着我们可能需要 级别求出单次询问的答案。

首先考虑找规律/猜结论。对于一个点 ,我们删掉之后这个连通块会变成多少个呢?当然是 个;然而,由于 是之前更小的点删去后分出来的连通块的新的最小点,所以实际上是变成 个,但是对于一个初始连通块本身就有一个。所以答案就是连通块数量+……吗?

随便画一个不是树的连通块,就发现一个点删掉之后并不会出现 个连通块。现在我们知道从连通块个数的增加上考虑思路有点困难了,不如直接考虑最后不被删除需要什么条件。

思考两年半,我们可以得知一个节点如果最后会留下来,那么它的邻居节点的编号一定均小于它,因为如果有大于它的它就会因为存在于一个大小大于 的连通块中删除。

所以我们直接用 set 动态维护每个节点的邻点编号,动态计算答案即可。

时间复杂度 ,当然可以通过标记的方式去掉 set

const int kMaxN = 1e5 + 5;
 
int n, m, q, ans;
set<int> e[kMaxN];
 
bool check(int x) {
  return e[x].empty() || *e[x].rbegin() < x;
}
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1, u, v; i <= m; i++) {
    cin >> u >> v;
    e[u].insert(v);
    e[v].insert(u);
  }
  for (int i = 1; i <= n; i++) {
    ans += check(i);
  }
  cin >> q;
  for (int o, u, v; q; q--) {
    cin >> o;
    if (o == 1) {
      cin >> u >> v;
      ans -= check(u);
      ans -= check(v);
      e[u].insert(v);
      e[v].insert(u);
      ans += check(u);
      ans += check(v);
    } else if (o == 2) {
      cin >> u >> v;
      ans -= check(u);
      ans -= check(v);
      e[u].erase(v);
      e[v].erase(u);
      ans += check(u);
      ans += check(v);
    } else {
      cout << ans << '\n';
    }
  }
  return 0;
}

排排队

暴力 找规律 结论 前缀和

重点

这种题目又出现了,就是优化枚举。普通的暴力枚举肯定过不了,但是对于大多数情况都不可能造成最优解的,直接不考虑。

思路

若干次合并相邻的两个元素可以看作区间合并为一个,于是想到异或前缀和,这样就可以 求出区间异或和了。

一个显而易见的结论是:

  • [n] 我们只会进行区间合成最多两次,因为只需要构造一对逆序的。

于是可以想出时间复杂度为 的暴力,即枚举左端点 和右端点 ,中间枚举 的合并, 的合并,满足的要求就是 。答案取

对于 ,直接输出 。此时就可以直接获得 分了。

疑问 还是其它?

为什么这样做是对的呢?怎么想到的?具体的边界真的是

证明

实际上边界是

对于 个相邻的数,如果它们最高 位是相同的,那么前两个异或之后这一位就没有了,所以答案是

为了避免这种情况,我们需要构造相邻 个数最高位均不同的,但是最高位不能单调递减(这样本身答案就是 了)。所以最好的构造方案是每个数位作为最高位构造两个数,最高数位单调递增。这样子就是 个数了。大于 必然崩盘,答案就为

我的做法更奇特。没有钦定对于 答案一定为 ,因为不保险。我钦定答案不会超过 ,所以对于每个 只枚举往后最多 个。

注意

这种做法的运算量高达 ,需要配合最优性剪枝才能通过。当然忽略常数时间复杂度

这是我的做法。

const int kMaxN = 2e5 + 5;
 
int p[kMaxN], a[kMaxN], t, n, ans;
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  for (cin >> t; t; t--) {
    cin >> n;
    for (int i = 1; i <= n; i++) {
      cin >> a[i];
      p[i] = p[i - 1] ^ a[i];
    }
    ans = 1e9;
    for (int i = 1; i <= n; i++) {
      for (int j = i + 2; j <= min(n, i + 60); j++) {
        if (j - i - 1 >= ans) {
          break;
        }
        for (int k = i; k < j; k++) {
          if ((p[k] ^ p[i - 1]) > (p[j] ^ p[k])) {
            ans = j - i - 1;
          }
        }
      }
    }
    cout << (ans != 1e9 ? ans : -1) << '\n';
  }
  return 0;
}

南斯拉夫

深度优先搜索 广度优先搜索 并查集

思路

我采用的暴力。

首先可以看作二选一,每个元素最后必须被选偶数次,于是 DFS 暴搜 判断优化为 判断后直接获得 分。