排队
思路
首先考虑如何支持等差,不难想到枚举差 然后在此方差的情况下选择修改次数最小的方案。由于它规定了值域是 ,而方差为 时目标 至少为 ,所以枚举上界就是 。这个枚举时间复杂度 。
对于第 个元素,我们生成基准目标元素 ,那么需要将原始 修改成和基准目标元素每个差值相同的(保证插值 )。具体来说,选择与基准元素差值相同数量最多的,最后需要修改的数量就越少。
这个检查是 线性的。但是由于需要用到负数下标,我使用了
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 暴搜 , 判断优化为 判断后直接获得 分。