AT_wtf22_day1_d Welcome to Tokyo! 题解

非常有意思的题目。

思路#

考虑如何统计答案。

我们设 ai 为是否在第 i 天举办宴会,bi 为第 i 个人是否参加过宴会。

那么有:

{ai1bi1bij=liriaji=1naik

这是一个线性规划形式,虽然我感觉初看原题的时候很难想到要写成这样的线性规划形式。

我们要统计的是:

maxi=1mbi

整理一下:

maxi=1mbi{ai10bi10bij=liriaj0i=1naik0ai,bi0

由于原问题并不是很好做,我们可以想到线性规划对偶来看一看。

可以对限制加上一些乘子变量放到式子中。

i=1mbi+i=1nxi(ai1)+i=1myi(bi1)+i=1mzi(bij=liriaj)+(i=1naik)u

然后以 ai,bi 为主元。

i=1m(1+yi+zi)bi+i=1n(xij=lirizj+u)aii=1nxii=1myiku

依据 ai,bi0 写出限制。

mini=1nxii=1myiku{1+yi+zi0xij=lirizj+u0xi,yi,zi,u0

整理一下。

mini=1nxi+i=1myi+ku{yi+zi1xi+uj=lirizjxi,yi,zi,u0

容易发现 yi+zi 肯定等于 1 时最优。

而使用 xi 去抵消 zj 的影响肯定不优,所以一定有 xi=0

那么问题又简化成:

minmi=1mzi+ku{zi1uj=lirizjzi,u0

它的意义是什么。

我们需要挑选出一个最大的线段集,使得每个点被覆盖的次数小于等于 u

不妨设答案为 fu

其中,f1 是经典贪心问题,而这个贪心结论放在 fi 同样成立,也就是不断的选取合法的右端点最小的线段。

并且又有结论是必定会有 fifi+1 的子集,所以我们可以增量构造。

使用线段树模拟即可。

时间复杂度:O(mlogn+n)

Code#

/*
  ! 如果没有天赋,那就一直重复
  ! Created: 2024/06/26 15:23:52
*/
#include <bits/stdc++.h>
using namespace std;

#define x first
#define y second
#define fro(i, x, y) for (int i = (x); i <= (y); i++)
#define pre(i, x, y) for (int i = (x); i >= (y); i--)

const int N = 1e6 + 10;

int n, m, l[N], r[N], d[N], w[N], s[N];
int v[N << 1];
int g[N << 1];
list<int> t[N << 1];

inline void upd(int p, int l, int r, int k, int x) {
  t[p].push_back(x);
  if (l == r) return;
  int mid = (l + r) >> 1;
  if (mid >= k) upd(mid<<1, l, mid, k, x);
  if (mid <  k) upd(mid<<1|1, mid + 1, r, k, x);
}
inline void ckm(int&x, int y) {
  if (x == 0) x = y; else if (y && r[x] > r[y]) x = y;
}
inline void pdo(int p, int mid) {
  if (g[p]) {
    g[mid<<1] += g[p], g[mid<<1|1] += g[p];
    v[mid<<1] += g[p], v[mid<<1|1] += g[p];
    g[p] = 0;
  }
}
inline void add(int p, int l, int r, int L, int R) {
  if (L <= l && r <= R) return g[p]++, v[p]++, void();
  int mid = (l + r) >> 1; pdo(p, mid);
  if (mid >= L) add(mid<<1, l, mid, L, R);
  if (mid <  R) add(mid<<1|1, mid + 1, r, L, R);
  v[p] = max(v[mid<<1], v[mid<<1|1]);
}
inline int ask(int p, int l, int r, int L, int R) {
  while (t[p].empty() == 0 && w[t[p].front()]) t[p].pop_front();
  if (t[p].empty()) return 0;
  if (L <= l && r <= R) return t[p].front();
  int mid = (l + r) >> 1, num = 0;
  if (mid >= L) ckm(num, ask(mid<<1, l, mid, L, R));
  if (mid <  R) ckm(num, ask(mid<<1|1, mid + 1, r, L, R));
  return num;
}
inline int ask(int p, int l, int r, int k) {
  if (l == r) return l;
  int mid = (l + r) >> 1; pdo(p, mid);
  return v[mid<<1|1] == k ? ask(mid<<1|1, mid + 1, r, k) : ask(mid<<1, l, mid, k);
}
inline int get(int i, int j) {
  return m - s[i] + j * i;
}

signed main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  fro(i, 1, m) {
    cin >> l[i] >> r[i];
  }
  iota(d + 1, d + m + 1, 1);
  sort(d + 1, d + m + 1, [&](int x, int y) {
    return r[x] < r[y];
  });
  fro(i, 1, m) upd(1, 1, n, l[d[i]], d[i]);
  fro(i, 1, m) {
    s[i] = s[i - 1];
    if (s[i] != m) {
      int las = 1;
      while (s[i] < m && las <= n) {
        int x = ask(1, 1, n, las, n);
        if (x == 0) break;
        w[x] = 1, s[i]++, add(1, 1, n, l[x], r[x]);
        if (v[1] == i) las = ask(1, 1, n, i) + 1;
      }
    }
  }
  int it = m;
  fro(i, 1, n) {
    while (it && get(it, i) > get(it - 1, i)) it--;
    cout << get(it, i) << "\n";
  }
  return 0;
}

作者:JiaY19

出处:https://www.cnblogs.com/JiaY19/p/18273663

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   JiaY19  阅读(21)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示