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 个区间 I1[l1,r1],I2[l2,r2],,In[ln,rn] 以及两个整数 A,B。保证任意一个区间都包含零点,即 li0,ri0,从中选出若干个区间 Ip1,Ip2,,Ipk,最大化:

A|Ip1Ip2···Ipk|+B|Ip1Ip2···Ipk|

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)。那么就检查所有的被包含在枚举区间的其他区间的左端点。然后取其中答案贡献最大的计算答案。

算法一

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

A|IpiIpj|+B|IpiIpj|=AriAlj+A+BrjBli+B=AriBliAlj+Brj+A+B

f(i)=AriBlig(i)=Alj+Brj,则答案总贡献为:

=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 的非负数列 ai,每一次操作可以将相邻两项合并,合并之后的元素为两个元素之和,并将序列大小 1。求将序列变为非降序列所需要的最少合并次数。

Solve#

考虑 dp。

dpi 表示将前 i 项变为非降序列所需要的最少合并次数,dp2i 表示将前 i 项变为非降序列后第 i 项的最小值,sumi 表示 ai 的前缀和。则有:

dpi=min0ji1,sumisumjdp2jdpj+ij1dp2i=min0ji1dp2j

时间复杂度 O(n2)

还没完,由于在 dpi 最小的同时要满足 dp2i 最小,而 sumisumj 是从左往右单调递增的,所以只要倒序枚举 j,当 dp2jsumisumj 时,直接更新答案,然后 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 个元素的总权值作为方案的代价。
显然, 一共有 kk 种合法的选择方案。求出这些方案中前 k 小的代价是多少。

Solve#

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

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

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

时间复杂度 O(k3logk),可以过(就很神奇。

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 条边构成的图,每条边有一个单向的流量,每一个点流入的流量之和等于流出的流量之和。现在你可以调查一条边的状态,代价为 wi(可以是负数)。需要最小化代价之和,并直接或间接得到所有边的状态。

Solve#

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

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

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

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

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

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

Kruskal 板子,时间复杂度 O(nlogn)

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;
}

作者:Daniel-yao

出处:https://www.cnblogs.com/Daniel-yao/p/17682686.html

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

posted @   Daniel_yzy  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示