ARC179 补

A partition

给定一个长度为 \(n\) 的序列 \(x\),求将其重排后满足条件的重排方案,或报告无解。

条件:构造一个序列 \(y\)\(y_0=0,y_i=\sum\limits_{j = 1}^{i}x_j\),满足小于 \(k\) 的数全部在大于等于 \(k\) 的数右侧。

Solution
  • \(k > 0\) 时,\(k\) 左侧为 \(0\),所以 \(0\) 不会对答案产生影响,直接排序即可。因为单调所以满足条件。

  • \(k \leq 0\) 时,\(k\) 右侧为 \(0\),所以右边的所有数必须比 \(0\) 大。可以倒序排序,这样可以让前缀和都尽量大,如果还不够条件就无解了。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2e5 + 1;

int n, k, a[kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> k;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  sort(a + 1, a + n + 1);
  if (k <= 0) {
    reverse(a + 1, a + n + 1);
    if (accumulate(a + 1, a + n + 1, 0ll) < k) {
      cout << "No\n";
      return 0;
    }
  }
  cout << "Yes\n";
  for (int i = 1; i <= n; i++) {
    cout << a[i] << ' ';
  }
  return 0;
}

B Between B and B

给定一个长度为 \(m(m\leq 10)\) 的序列 \(x\),求满足条件的长度为 \(n\) 的序列 \(a\) 个数。

条件:\(a\) 值域 \([1,m]\),且若 \(a_l=a_r=k\),则 \(\exist b\in[l,r],b = x_k\)

Solution

因为 \(m\) 极小,可以考虑状压每个 \(k=a_i\) 有没有出现后面的 \(x_k\)

即令 \(f_{i, s}\) 表示当前考虑到 \(i\)\(s\) 的第 \(j\) 位表示 \(j\) 后面有没有出现 \(x_j\)

随便转移。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <deque>
#include <iostream>
#include <map>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e3 + 1;

int n;
map<PII, int> M;
bool cmp(int i, int j) {
  if (M.count({i, j})) {
    return M[{i, j}];
  }
  bool ans;
  cout << "? " << i << ' ' << j << endl;
  cin >> ans;
  return M[{i, j}] = ans;
}
deque<int> q;
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1; i <= n; i++) {
    q.push_back(i);
  }
  sort(q.begin(), q.end(), cmp);
  for (int i = n; i >= 2; i--) {
    cout << "+ " << q.front() << ' ' << q.back() << endl;
    q.pop_front(), q.pop_back();
    int id;
    cin >> id;
    q.insert(lower_bound(q.begin(), q.end(), id, cmp), id);
  }
  cout << "!" << endl;
  return 0;
}

C Beware of Overflow

给定一个整数 \(n\leq 1000\),在交互库内有一个整数 \(r\) 和一个长度为 \(n\) 的序列 \(a\),保证 \(a_i<r,\sum a_i<r\)。请通过不超过 \(25000\) 次操作将序列 \(a\) 合并为同一个数。

  • 操作 1:+ i j 表示将 \(a_i\)\(a_j\) 删除并将其和插入序列末端,返回其下标。
  • 操作 2: ? i j 表示询问 \(a_i\)\(a_j\) 大小关系,返回 \([a_i<a_j]\)
Solution

可以利用操作 2 做 cmp,直接 sort,每次把最大值和最小值相加,直接把新数插入序列中,位置直接 lower_bound。显然是 \(2n\log n\) 次操作的。

这个题目让我知道了 sort 和 lower_bound 对 cmp 调用次数是不超过 \(n\log n\) or \(\log n\) 的。这很帅欸。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <deque>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e3 + 1;

int n;
bool cmp(int i, int j) {
  bool ans;
  cout << "? " << i << ' ' << j << endl;
  cin >> ans;
  return ans;
}
deque<int> q;
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1; i <= n; i++) {
    q.push_back(i);
  }
  sort(q.begin(), q.end(), cmp);
  for (int i = n; i >= 2; i--) {
    cout << "+ " << q.front() << ' ' << q.back() << endl;
    q.pop_front(), q.pop_back();
    int id;
    cin >> id;
    q.insert(lower_bound(q.begin(), q.end(), id, cmp), id);
  }
  cout << "!" << endl;
  return 0;
}

D Portable Gate

给定一棵大小为 \(n\) 的树,起始点终止点自定,问最少付出多少代价可以遍历完整棵树。

操作:放置传送门或回到传送门,代价为 \(0\);走一条边,代价为 \(1\)

Solution

显然按 dfs 序遍历代价最小。无确定起始点,没有根,可以自定,然后换根 dp。

不难猜到一个错误的结论:每次遇到分叉就放传送门。反例:一条链挂一个分叉,最小代价方案为在链顶放传送门。

走法的结论实在是没有了,不如直接考虑树形 dp。

\(f_{i, 0/1}\) 表示遍历完以 \(i\) 为根的子树,不需要 or 必须回到 \(i\) 所付出的最小代价。

\(f_{i, 1}\) 有两种方案,一种是从 \(i\) 的儿子转移过来,父子边直接走。另一种方案是在 \(i\) 这里放传送门,走遍整个子树传送回来,就是 \(2siz_i-dep_i\)

\(f_{i, 0}\) 类似的,选择一个儿子不出来,要出来的儿子处理方法和 \(f_{i, 1}\) 基本相同。不出来可以直接走父子边转移。

换根即可。

没写程序,这么恶心的换根谁爱写谁写。

E Rectangle Concatenation

给定 \(n\) 个矩形,问其中有几个区间满足条件。

条件:按原序列顺序拼合矩形,可以在拼合的过程中一直保持拼出来的图形是矩形。

Solution

考虑 \(O(n^2)\) 做法是怎么做的。

可以枚举区间左端点,在右端点右移的时候,即添加新的矩形,暴力模拟即可。

模拟是绝对没办法优化的,考虑表示成一个 \(O(n^2)\) 的 dp。

枚举左端点 \(l\),令 \(f_{r, 0/1}\) 表示第 \(r\) 个矩形在拼合时是 x/y 相同而拼是否可能。

考虑 \(f_{r, 0}\)\(f_{r,1}\) 旋转而完全相同,无需特别考虑。从 \(f_{r-1, 0}\)\(f_{r-1, 1}\) 转移过来。

若从 \(f_{r-1,0}\) 转移,则整体矩形的 \(x=x_{r-1}\)。所以只要 \(x_r=x_{r-1}\) 即可转移过来。

\(f_{r-1,1}\) 来转移的话,整体矩形的 \(x\) 不好直接算,记录上次出现 \(0\) 加区间和也是没有前途。但是可以计算出区间矩形的面积之和去除以 \(y_{r-1}\),因为整体矩形的 \(y=y_{r-1}\)

思考一下,这个 dp 转移,它在哪里浪费了时间?

你发现了,枚举 \(l\)\(r\) 只能一个一个加矩形,这样加入矩形次数达到了 \(O(n^2)\)

但是如果我换一下,枚举 \(r\)\(l\) 呢?这样我的区间右端点固定,每次加矩形总是加一整个序列的,我加矩形的次数仅仅 \(O(n)\)

可是怎么转移呢?你突然发现,\(O(n^2)\) 做法的第一种转移方式是 \(f_{r-1, 0}\)\([x_r=x_{r-1}]\) 取并,这个转移方式就导致每次改变右端点,要么继承上一次的答案,要么直接删除,以后再也不可能这么拼。第二种转移方式是拿面积和去除以 \(y_{r-1}\) 要等于 \(x_r\),但是我现在知道 \(y_{r-1}\) 也知道 \(x_r\),是不是面积和是一定的?那面积和一定,是不是对应着产生改变的 \(y\) 也是一定的,只有一个。

均摊下来 \(O(n)\)

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <unordered_map>
#include <vector>

using namespace std;
using LL = long long;
using Rec = pair<int, int>;
constexpr int kN = 3e5 + 1;
#define x first
#define y second

int n;
Rec a[kN];
LL s[kN], ans, cur;
unordered_map<LL, int> M;
bool t[2][kN];
vector<int> v[2];

void R(int tp, int x) {
  cur -= (t[tp][x] == 1 && t[!tp][x] == 0);
  t[tp][x] = 0, v[tp].pop_back();
}
void A(int tp, int x) {
  cur += (t[tp][x] == 0 && t[!tp][x] == 0);
  t[tp][x] = 1, v[tp].push_back(x);
}

int main() {
  freopen("hack.txt", "r", stdin);
  cin.tie(0)->sync_with_stdio(0);
  cin >> n, M[0] = 0;
  for (int i = 1; i <= n; i++) {
    cin >> a[i].x >> a[i].y;
    s[i] = s[i - 1] + 1ll * a[i].x * a[i].y;
    M[s[i]] = i;
  }
  auto F = [](LL x) { return M.count(x) ? M[x] + 1 : -1; };
  for (int i = 1; i <= n; i++) {
    int px = i >= 2 ? F(s[i - 1] - 1ll * a[i].x * a[i - 1].y) : -1;
    int py = i >= 2 ? F(s[i - 1] - 1ll * a[i].y * a[i - 1].x) : -1;
    bool tx = px == -1 ? 0 : t[1][px];
    bool ty = py == -1 ? 0 : t[0][py];
    if (a[i].x != a[i - 1].x) {
      for (; !v[0].empty(); R(0, v[0].back())) {
      }
    }
    if (a[i].y != a[i - 1].y) {
      for (; !v[1].empty(); R(1, v[1].back())) {
      }
    }
    tx && !t[0][px] && (A(0, px), 0);
    ty && !t[1][py] && (A(1, py), 0);
    A(0, i), A(1, i);
    ans += cur;
  }
  cout << ans << '\n';
  return 0;
}
posted @ 2024-07-13 19:38  Lightwhite  阅读(29)  评论(0)    收藏  举报