Loading

test20230906考试总结

前言

这一场比赛已经彻底让我自闭了,看不起 whk 的我逐渐开始搞 whk 了,可见我有多自闭了。

赛时得分明细:

intervals trans ez traffic Total
40 60 0 0 100

改题明细:

intervals trans ez traffic Total
100 90 100 100 390

改题还算有进步的,只是效率慢了点qwq。

为了方便以后复习,所以每一道题都搬到了 luogu 上 ,由于版权问题,仅个人可见。

intervals

Problem

给定 \(n\) 个区间 \(I_1[l_1,r_1],I_2[l_2,r_2],\dots,I_n[l_n,r_n]\) 以及两个整数 \(A,B\)。保证任意一个区间都包含零点,即 \(l_i\le 0,r_i\ge 0\),从中选出若干个区间 \(I_{p_1},I_{p_2},\dots,I_{p_k}\),最大化:

\[A|I_{p_1}\cap I_{p_2}\cap ··· \cap I_{p_k}| + B|I_{p_1}\cup I_{p_2}\cup ··· \cup I_{p_k}| \]

Solve

分类讨论:

  1. \(A<0,B<0\) 时,一个区间都不能选,因为一旦选了哪怕一个区间就会做负贡献了。
  2. \(A<0,B>0\) 时,所有区间都要选,由于所有的区间都包含零点,所以所有区间都有交。现在想要让交的区间长度更小,并的区间长度更大。选上所有区间一定不会更劣。
  3. \(A>0,B<0\) 时,至少选一个区间,但当 \(|B|>|A|\) 时,负贡献更多,那还不如不选。
  4. \(A>0,B>0\) 时,考虑所有可能的并都能用至多两个区间表示出来,并且交集不为空。所以暴力的做法是枚举两个区间然后统计答案。

这里有一个小 trick:对于第 \(4\) 种情况来说,考虑按左端点从大到小排序,枚举一个区间,由于另一个区间肯定不能让枚举的区间所包含(设想一下,如果交了,只是交集变小了,并集不变,答案贡献只会变小,还不如不选qwq)。那么就检查所有的被包含在枚举区间的其他区间的左端点。然后取其中答案贡献最大的计算答案。

算法一

这里需要推一下式子:令 \(I_i\) 为枚举的区间,\(I_j\) 为左端点包含在 \(I_i\) 内且整体不被包含于 \(I_i\) 内的区间。则有

\[\begin{aligned} A|I_{p_i}\cap I_{p_j}| + B|I_{p_i}\cup I_{p_j}|&= Ar_i-Al_j+A+Br_j-Bl_i+B\\ &=Ar_i-Bl_i - Al_j+Br_j+A+B \end{aligned} \]

\(f(i)=Ar_i-Bl_i\)\(g(i)=-Al_j+Br_j\),则答案总贡献为:

\[=f(i)+g(j)+A+B \]

枚举 \(f(i)\),线段树去维护 \(g(j)\),再统计答案即可(记得将被包含的区间扔掉)。

算法二

可以注意到,答案是一边枚举一边计算贡献的。所以从前往后枚举,所以 \(g(j)\)\(f(i)\) 要枚举到。记录一下前缀 \(g(j)\) 最大值即可。

Code

算法一:权值线段树写法

#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
#define inf 1e18
using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

const int N = 1e5 + 10;

struct Node {
  int l, r;
} a[N], b[N];

struct Seg_tree {
  int l, r, mx;
} t[N * 100];

int n, len, A, B, ans = -inf, idx, rt, lmin = inf, rmin = inf, lmax = -inf, rmax = -inf;

bool cmp(Node x, Node y) {
  return (x.l == y.l ? x.r > y.r : x.l < y.l);
}

void pushup(int p) {
  t[p].mx = max(t[t[p].l].mx, t[t[p].r].mx);
}

void up(int &p, int l, int r, int k, int val) {
  if(!p) p = ++idx, t[p].mx = -inf;
  if(l == r) {
    t[p].mx = max(t[p].mx, val);
    return ;
  }
  int mid = (l + r) >> 1;
  if(k <= mid) up(t[p].l, l, mid, k, val);
  else up(t[p].r, mid + 1, r, k, val);
  pushup(p);
}

int ask(int p, int l, int r, int ql, int qr) {
  if(ql <= l && r <= qr) {
    return t[p].mx;
  }
  int mid = (l + r) >> 1, ans = -inf;
  if(ql <= mid) ans = max(ans, ask(t[p].l, l, mid, ql, qr));
  if(qr > mid) ans = max(ans, ask(t[p].r, mid + 1, r, ql, qr));
  return ans;
}

signed main() {
  n = read();
  For(i,1,n) a[i] = (Node){read(), read()};
  For(i,1,n) {
    lmin = min(lmin, a[i].l);
    lmax = max(lmax, a[i].l);
    rmin = min(rmin, a[i].r);
    rmax = max(rmax, a[i].r);
  }
  A = read(), B = read();
  sort(a + 1, a + n + 1, cmp);
  int maxn = -inf;
  For(i,1,n) {
    if(maxn < a[i].r) a[++len] = a[i];
    maxn = max(maxn, a[i].r);
  }
  n = len;
  ans = max(0ll, A * (rmin - lmax + 1) + B * (rmax - lmin + 1));
  For(i,1,n) {
    up(rt, -1e9, 1e9, a[i].l, (-a[i].l * A + B * a[i].r));
  }
  For(i,1,n) {
    ans = max(ans, ask(rt, -1e9, 1e9, a[i].l, a[i].r) + a[i].r * A - B * a[i].l + A + B); 
  }
  cout << ans << '\n';
  return 0;
}

算法二:前缀 Max 写法

#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
#define inf 1e18
using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

const int N = 1e5 + 10;

struct Node {
  int l, r;
} a[N];

int n, len, A, B, ans = 0, lmin = inf, rmin = inf, lmax = -inf, rmax = -inf, maxn;

bool cmp(Node x, Node y) {
  return (x.l == y.l ? x.r > y.r : x.l < y.l);
}

signed main() {
  n = read();
  For(i,1,n) a[i] = (Node){read(), read()};
  A = read(), B = read();
  For(i,1,n) {
    lmin = min(lmin, a[i].l);
    lmax = max(lmax, a[i].l);
    rmin = min(rmin, a[i].r);
    rmax = max(rmax, a[i].r);
    maxn = max(maxn, a[i].r - a[i].l + 1);
  }
  if(A <= 0 && B <= 0) ans = 0;
  else if(A <= 0 && B > 0) ans = A * (rmin - lmax + 1) + B * (rmax - lmin + 1);
  else if(A > 0 && B <= 0) ans = max(ans, (A + B) * maxn);
  else {
    sort(a + 1, a + n + 1, cmp);
    int maxi = -inf, maxx = -inf;
    For(i,1,n) {
      if(maxx < a[i].r) a[++len] = a[i];
      maxx = max(maxx, a[i].r);
    }
    n = len;
    For(i,1,n) {
      maxi = max(maxi, A * a[i].r - B * a[i].l);
      ans = max(ans, maxi + A * (-a[i].l) + B * a[i].r + A + B);
    }
  }
  cout << ans << '\n';
  return 0;
}

trans

Problem

给定一个长度为 \(n\) 的非负数列 \(a_i\),每一次操作可以将相邻两项合并,合并之后的元素为两个元素之和,并将序列大小 \(-1\)。求将序列变为非降序列所需要的最少合并次数。

Solve

考虑 dp。

\(dp_i\) 表示将前 \(i\) 项变为非降序列所需要的最少合并次数,\(dp2_i\) 表示将前 \(i\) 项变为非降序列后第 \(i\) 项的最小值,\(sum_i\) 表示 \(a_i\) 的前缀和。则有:

\[\begin{aligned} &dp_i=\min_{0\le j\le i-1,sum_i-sum_j\ge dp2_j}dp_j + i - j - 1 \\ &dp2_i=\min_{0\le j\le i-1}dp2_j \end{aligned} \]

时间复杂度 \(O(n^2)\)

还没完,由于在 \(dp_i\) 最小的同时要满足 \(dp2_i\) 最小,而 \(sum_i-sum_j\) 是从左往右单调递增的,所以只要倒序枚举 \(j\),当 \(dp2_j\le sum_i-sum_j\) 时,直接更新答案,然后 break。

这样可以多过一个点,拿到 \(90pts\) 的好成绩。

Code

以下时 \(90pts\) 的代码:

#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

const int N = 3e6 + 10;

int n, a[N], sum[N], dp[N], dp2[N], lastj;

signed main() {
  n = read();
  For(i,1,n) a[i] = read(), sum[i] = sum[i-1] + a[i];
  memset(dp, 0x3f, sizeof dp);
  memset(dp2, 0x3f, sizeof dp2);
  dp[0] = 0, dp2[0] = 0;
  For(i,1,n) {
    FOR(j,i-1,lastj) {
      int x = sum[i] - sum[j];
      if(x >= dp2[j]) {
        dp[i] = dp[j] + i - j - 1;
        dp2[i] = x;
        lastj = j;
        cout << j << '\n'; 
        break;
      }
    }
    For(j,lastj,i-1) cout << dp2[j] << ' ';
    cout << '\n';
  }
  cout << dp[n] << '\n';
  return 0;
}

ez

Problem

给出 \(k\) 个大小为 \(k\) 的集合,集合中的每个元素带权。一个合法的选择方案从每个集合中选出恰好一个元素, 且获得这 \(k\) 个元素的总权值作为方案的代价。
显然, 一共有 \(k^{k}\) 种合法的选择方案。求出这些方案中前 \(k\) 小的代价是多少。

Solve

正解不太会,但是可以用暴力创!!

操作以每一行为基准,每一次从优先队列里取出前 \(k\) 大的数,并保留下来,其他的数弹出舍弃,然后将这些数与这一行的所有数两两匹配求和,放入优先队列中,然后进入下一行,以此类推。

这样最后优先队列里的数一定是前 \(k\) 大的。

时间复杂度 \(O(k^3\log k)\),可以过(就很神奇。

Code

#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

const int N = 6e2 + 10, M = 3e6 + 10;

int n, a[N][N];

signed main() {
  while(scanf("%d", &n) != EOF) {
    priority_queue<int> q;
    vector<int> p;
    For(i,1,n) For(j,1,n) a[i][j] = read();
    For(i,1,n) {
      if(i == 1) {
        For(j,1,n) q.push(a[i][j]); 
        continue;
      } 
      p.clear();
      while(!q.empty()) {
        p.push_back(q.top()); q.pop();
      }
      For(j,1,n) {
        FOR(k,n-1,0) { 
          int x = p[k] + a[i][j];
          if(q.size() != n) q.push(x);
          else if(q.top() > x) q.pop(), q.push(x);
          else break; 
        }
      }
    }
    p.clear();
    while(!q.empty()) {
      p.push_back(q.top()); q.pop();
    }
    FOR(i,n-1,0) cout << p[i] << ' ';
    cout << '\n'; 
  }
  return 0;
}

traffic

Problem

给定一张由 \(n\) 个点 \(m\) 条边构成的图,每条边有一个单向的流量,每一个点流入的流量之和等于流出的流量之和。现在你可以调查一条边的状态,代价为 \(w_i\)(可以是负数)。需要最小化代价之和,并直接或间接得到所有边的状态。

Solve

正难则反,考虑保留(不调查)哪些边可以推出所有边。

通过手模样例可得,边的状态(连边方向)与是否能推出边无关,只在乎一个点的入度和出度。

可以发现,假如保留的边形成了环,则其一定不合法。换句话说,保留的边不能有环。大胆猜测其形态为一棵树。

现在是要被调查的边,也就是被删除的边的代价尽可能小,那么,保留的边的代价要尽可能大。所以,大胆猜测其为最大生成树。

最后答案为总代价之和减去最大生成树的代价之和。

由于负代价边选了一定不会劣,所以要全选上,所以保留的边所组成的图为最大生成树森林。求最大生成树的时候要将负代价边去掉,图不一定连通。

\(Kruskal\) 板子,时间复杂度 \(O(n\log n)\)

Code

#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

const int N = 5e5 + 10;

struct Node {
  int u, v, w;
} e[N];

int n, m, f[N], ans, tot, W;

int find(int x) {
  return (x == f[x] ? x : f[x] = find(f[x]));
}

bool cmp(Node x, Node y) {
  return x.w > y.w;
}

void kurskal() {
  sort(e + 1, e + m + 1, cmp);
  For(i,1,m) {
    int u = e[i].u, v = e[i].v;
    if(find(u) == find(v)) continue;
    f[find(u)] = find(v);
    tot++;
    ans += e[i].w;
    if(tot == n-1) break;
  }
}

signed main() {
  n = read(), m = read();
  For(i,1,n) f[i] = i;
  For(i,1,m) {
    int u = read(), v = read(), w = read();
    W += w; 
    if(w > 0) e[i] = (Node){u, v, w};
  }
  kurskal();
  cout << W - ans << '\n';
  return 0;
}
posted @ 2023-09-06 16:33  Daniel_yzy  阅读(8)  评论(0编辑  收藏  举报