首页 > 其他分享 >牛客暑假多校 2023 第六场

牛客暑假多校 2023 第六场

时间:2023-08-05 13:55:08浏览次数:49  
标签:第六场 int ll 多校 kN 牛客 cnt include sum5

目录

写在前面

比赛地址:https://ac.nowcoder.com/acm/contest/57360

哈基米牛魔酬宾,哈比下,哈比下,奥利安费,阿米诺斯!

以下按照个人向难度排序。

G

\(a - b\) 相当于辗转相减,\(\gcd(|a|, |b|)\) 和直接 \(\gcd\) 没什么区别。

于是当 \(z= 0\) 时,\(x,y\) 中一者为 0 则 YES,否则 NO;当 \(z \not= 0\) 时判断 \(z\) 是否是 \(\gcd(x, y)\) 的倍数。

Code by Nebulyu:

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void solve(){
	ll x,y,z;cin>>x>>y>>z;
	ll g=gcd(x,y);
	if(z==0){
		if(x==0||y==0)cout<<"YES\n";
		else cout<<"NO\n";
		return ;
	}
	if(z%g)cout<<"NO\n";
	else cout<<"YES\n";
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T;cin>>T;while(T--)solve();
	return 0;
}

E

鉴于偶数加偶数还是偶数,显然如果一段区间可以被分成 \(k\) 份偶数区间,那么它最多可以被分成不小于 \(k\) 份偶数区间,于是仅需考虑每个区间最多可以分成多少个偶数部分。

赛时想了个恼弹线段树,先将询问按右端点排序,然后考虑枚举右端点,当前一段的和为偶数则进行分割,仅需查询区间被分割的数量即可。这个过程可以用线段树维护,如果这个位置为奇数则在线段树该位置改为 0 并将之前位置全部取反,如果为偶数则将线段树该位置改为 1。枚举过程中查询区间和即可,总时间复杂度 \(O((n + q)\log n)\)。

实际上根本不需要线段树,维护一个数量的前缀和和奇偶性的前缀和即可,总时间复杂度 \(O(n + q)\)。

//
/*
By:Luckyblock
*/
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, q;
struct AdmireVega {
  int l, r, k, id;
} ayabe[kN];
bool a[kN], ans[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
bool cmp(AdmireVega fir_, AdmireVega sec_) {
  return fir_.r < sec_.r;
}
namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  const int kNode = kN << 2;
  int sum[kNode], tag[kNode];
  void Pushup(int now_) {
    sum[now_] = sum[ls] + sum[rs];
  }
  void Pushdown(int now_, int L_, int R_) {
    sum[ls] = (mid - L_ + 1) - sum[ls];
    sum[rs] = (R_ - mid) - sum[rs];
    tag[ls] ^= 1, tag[rs] ^= 1;
    tag[now_] = 0;
  }
  void Insert(int now_, int L_, int R_, int pos_, int val_) {
    if (L_ == R_) {
      sum[now_] = val_;
      return ;
    }
    if (tag[now_]) Pushdown(now_, L_, R_);
    if (pos_ <= mid) Insert(ls, L_, mid, pos_, val_);
    else Insert(rs, mid + 1, R_, pos_, val_);
    Pushup(now_);
  }
  void Reverse(int now_, int L_, int R_, int l_, int r_) {
    if (l_ <= L_ && R_ <= r_) {
      sum[now_] = (R_ - L_ + 1) - sum[now_];
      tag[now_] ^= 1;
      return ;
    }
    if (tag[now_]) Pushdown(now_, L_, R_);
    if (l_ <= mid) Reverse(ls, L_, mid, l_, r_);
    if (r_ > mid) Reverse(rs, mid + 1, R_, l_, r_);
    Pushup(now_);
  }
  int QuerySum(int now_, int L_, int R_, int l_, int r_) {
    if (l_ <= L_ && R_ <= r_) return sum[now_];
    if (tag[now_]) Pushdown(now_, L_, R_);
    int ret = 0;
    if (l_ <= mid) ret += QuerySum(ls, L_, mid, l_, r_);
    if (r_ > mid) ret += QuerySum(rs, mid + 1, R_, l_, r_);
    return ret;
  }
  bool QueryPos(int now_, int L_, int R_, int pos_) {
    if (L_ == R_) return sum[now_];
    if (tag[now_]) Pushdown(now_, L_, R_);
    if (pos_ <= mid) return QueryPos(ls, L_, mid, pos_);
    return QueryPos(rs, mid + 1, R_, pos_);
  }
  #undef ls
  #undef rs
  #undef mid
}
void Init() {
  n = read(), q = read();
  for (int i = 1; i <= n; ++ i) {
    LL nowa; scanf("%lld", &nowa);
    a[i] = (nowa % 2ll == 1ll);
  }
  for (int i = 1; i <= q; ++ i) {
    int l_ = read(), r_ = read(), k_ = read();
    ayabe[i] = (AdmireVega) {l_, r_, k_, i}; 
  }
  std::sort(ayabe + 1, ayabe + q + 1, cmp);
}
void Solve() {
  for (int nowr = 1, nowq = 1; nowr <= n; ++ nowr) {
    if (nowr > 1 && a[nowr] == 1) Seg::Reverse(1, 1, n, 1, nowr - 1);
    Seg::Insert(1, 1, n, nowr, (a[nowr] == 0));
    while (nowq <= q && ayabe[nowq].r == nowr) {
      int ret1 = Seg::QuerySum(1, 1, n, ayabe[nowq].l, nowr);
      int ret2 = Seg::QueryPos(1, 1, n, ayabe[nowq].l);
      ans[ayabe[nowq].id] = ret2 && (ret1 >= ayabe[nowq].k);
      ++ nowq;
    }
  }
  for (int i = 1; i <= q; ++ i) printf("%s\n", ans[i] ? "YES" : "NO");
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    Init();
    Solve();
  }
  return 0;
}

C

《我推的式子》

套路地转化为求因子 2 和 5 数量的较小值,显然因子 5 的数量更小,仅需关注如何求因子 5 个数即可。由于 5 的幂的贡献递增,手玩下发现答案即 \(n!!\) 中 5 的倍数的个数,加 25 的倍数的个数,加 625 的倍数的个数……

上面这东西可以分奇偶讨论,枚举 5 的幂次后转化为求等差数列和,详见代码。复杂度 \(O(\log n)\) 级别。

会爆 int128,赛时写了个 py。

py 里的 / 是实数除法,// 是整除,铭记!

def calc(x_):
  return x_ * (x_ + 1) // 2

n = int(input())
sum5 = 0

d = 5
for i in range(0, 28):
  if d > n:
    break
  cnt = n // d
  
  if cnt > 0:
    odd = cnt // 2
    even = (cnt - 1) // 2
    
    sum5 = sum5 + (d + 1) // 2 * calc(odd)
    sum5 = sum5 + d // 2 * calc(even)

    if cnt % 2 == 1:
      sum5 = sum5 + (n - (d * cnt - 1) + 1) // 2 * ((cnt + 1) // 2)
    else:
      sum5 = sum5 + (n - (d * cnt - 1)) // 2 * (cnt // 2)

  d = d * 5

d = 10
for i in range(0, 28):
  if d > n:
    break
  cnt = n // d
  if cnt > 0:
    sum5 = sum5 + d // 2 * calc(cnt - 1)
    sum5 = sum5 + (n - (d * cnt - 1) + 1) // 2 * cnt

  d = d * 5

print(int(sum5))

B

先排个序。

显然若两个集合可以互相转化,那么它们大小相同且一定是第 \(k\) 大转化为第 \(k\) 大。\(a_i\) 转化为 \(b_j\) 的操作次数显然为 \(|a_i - b_j|\),于是考虑 \(O(n^2)\) 地枚举被转化的数对,并考虑包含这些数对的集合的贡献。

设 \(f_{i, j}\) 表示从 \(a_1\sim a_{i}\) 和 \(b_1\sim b_j\) 中选择相同个数的数的方案数,\(g_{i, j}\) 表示从 \(a_i\sim a_{n}\) 和 \(b_j\sim b_n\) 中选择相同个数的数的方案数,显然 \((a_i, b_j)\) 对答案的贡献为:

\[f_{i - 1, j - 1}\times |a_i - b_j|\times g_{i + 1, j + 1} \]

如何求 \(f,g\) 呢?考虑组合意义,不妨令 \(i\le j\),则有:

\[f_{i, j} = \sum_{k = 0}^{i} {i\choose k}{j\choose k} \]

根据简单的组合数学推导一下:

\[f_{i, j} = \sum_{k = 0}^{i} {i\choose k}{j\choose k} = \sum_{k = 0}^{i} {i\choose i - k}{j\choose k} \]

发现这东西是个经典的范德蒙德卷积的形式,其组合意义相当于先在前 \(i\) 个里面选择 \(i - k\) 个,再在后 \(j\) 个物品中选择 \(k\) 个,等价于直接从 \(i + j\) 个物品中选择 \(i\) 个,则有:

\[f_{i, j} = {i + j\choose i} \]

同理 \(g\) 也可以轻松处理了,预处理后直接枚举 \((a_i, b_j)\) 并计算贡献即可。总复杂度 \(O(n^2)\) 级别。

Code by Nebulyu:

#include<bits/stdc++.h>
#define ffor(i,a,b) for(int i=a;i<=b;++i)
#define rfor(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
using ll=long long;
const ll P=998244353;
const int LIM=1e5;
const int M=LIM+5;
const int N=2e3+5;
ll fac[M],ifac[M];
ll C(ll n,ll r){return fac[n]*ifac[r]%P*ifac[n-r]%P;}
ll fsp(ll b,ll k){
	ll r=1;for(;k;k>>=1,b=b*b%P)
	if(k&1)r=r*b%P;return r;
} 
ll rec[N][N];
void init(){
	fac[0]=ifac[0]=1;
	ffor(i,1,LIM)fac[i]=fac[i-1]*i%P;
	ifac[LIM]=fsp(fac[LIM],P-2);
	rfor(i,LIM-1,1)ifac[i]=ifac[i+1]*(i+1)%P;
//	ffor(i,0,N-1)rec[i][0]=rec[0][i]=1;
	ffor(i,0,N-1)ffor(j,0,N-1)rec[i][j]=C(i+j,min(i,j));
}
int n;
ll f1[N],f2[N];
signed main(){
	init();
	cin>>n;
	ffor(i,1,n)cin>>f1[i];
	ffor(i,1,n)cin>>f2[i];
	sort(f1+1,f1+1+n);
	sort(f2+1,f2+1+n);
	ll ans=0;
	ffor(i,1,n)ffor(j,1,n){
		ll l1=i-1,l2=j-1,r1=n-i,r2=n-j;
		ans+=rec[l1][l2]*rec[r1][r2]%P*abs(f1[i]-f2[j])%P;
		ans%=P;
	}
	cout<<ans;
	return 0;
} 

A

场上想到 Kruscal 重构树了但是光想着预处理出两点间的最长边怎么网络流了思路歪了呃呃呃呃

两点间的贡献是路径上的最长边这个性质太典了,考虑 Kruscal 重构树,将原图节点变为重构树中的叶节点,两点间的最长边贡献变为两点 lca 的点权值的贡献,枚举边时将两个子树合并,两子树代表的点集之间的最长边即为枚举到的边的边权,仅需考虑两个子树中分别有多少黑白点即可。

于是可以在 Kruscal 重构树上得到一个很显然的树形 DP,设 \(f_{u, i}\) 表示在 \(u\) 的子树中一共有 \(i\) 个点为黑点时这棵树代表的点集对答案的贡献。如果节点 \(u\) 初始时为白点则初始化 \(f_{u, 0} = 0, f_{u, 1} = -\operatorname{cost}_u\),否则初始化 \(f_{u, 0} = -\operatorname{cost}_u, f_{u, 1} = 0\)。在构建 Kruscal 重构树枚举到边 \((u, v, w)\) 合并 \(u, v\) 所在点集 \(r_u, r_v\) 得到点集 \(r\) 的同时进行转移,考虑从 \(r_u, r_v\) 中分别选择多少个黑点,有:

\[f_{r, i} = \max_{0\le k\le \operatorname{sz}_{r_u},\ 0\le i - k \le \operatorname{sz}_{r_v}} \left\{ f_{r_u, k} + f_{r_v, i - k} + w\times (k\times (\operatorname{sz}_{r_v} - i + k) + (i - k)\times (\operatorname{sz}_{r_u} - k)) \right\} \]

设最终得到的点集为 \(rt\),答案即:

\[\max_{i = 0}^{n} f_{rt, i} \]

这样实现起来比较符合重构树的思路也比较直观,但是时间复杂度上限是 \(O(n^3)\) 级别,跑不过去。发现新建节点 \(rt\) 表示合并后的点集是没有必要的,可以在维护并查集时直接按秩合并到原节点上。于是修改定义 \(f_{u, i}\) 表示以 \(u\) 为根的点集中一共有 \(i\) 个点为黑点时这棵树代表的点集对答案的贡献,其余部分均不变。

并查集合并时使用按秩合并时,总时间度复杂度变为 \(O(n^2)\) 级别,甚至只跑了 111ms。

//
/*
By:Luckyblock
*/
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define LL long long
const int kN = 3e3 + 10;
const LL kInf = 1e18 + 2077;
//=============================================================
int n, rt, a[kN];
int fa[kN], sz[kN];
struct Edge {
  int u, v, w;
} e[kN];
LL f[kN][kN], temp[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
bool cmp(Edge fir_, Edge sec_) {
  return fir_.w < sec_.w;
}
int Find(int x_) {
  return fa[x_] == x_ ? x_ : fa[x_] = Find(fa[x_]);
}
void Merge(int x_, int y_, LL w_) {
  int fax = Find(x_), fay = Find(y_);
  int faxy = sz[fax] > sz[fay] ? fax : fay;

  for (int i = 0; i <= sz[fax] + sz[fay]; ++ i) {
    temp[i] = -kInf;
    for (int j = std::max(0, i - sz[fay]); j <= std::min(i, sz[fax]); ++ j) {
      int k = i - j;
      LL szx0 = sz[fax] - j, szy0 = sz[fay] - k;
      temp[i] = std::max(temp[i], f[fax][j] + f[fay][k] 
                                    + w_ * szx0 * k 
                                    + w_ * j * szy0);
    }
  }

  fa[fax] = fa[fay] = faxy;
  sz[faxy] = sz[fax] + sz[fay];
  for (int i = 0; i <= n; ++ i ) f[faxy][i] = temp[i];
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  n = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  for (int i = 1; i <= n; ++ i) {
    for (int j = 0; j <= n; ++ j) {
      f[i][j] = -kInf;
    }
  }
  for (int i = 1; i <= n; ++ i) {
    int cost = read();
    if (a[i]) f[i][1] = 0, f[i][0] = -cost;
    else f[i][1] = -cost, f[i][0] = 0;
  }

  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    e[i] = (Edge) {u_, v_, w_};
  }
  std::sort(e + 1, e + n, cmp);
  
  for (int i = 1; i <= n; ++ i) fa[i] = i, sz[i] = 1;
  for (int i = 1; i < n; ++ i) {
    int u_ = e[i].u, v_ = e[i].v, w_ = e[i].w;
    Merge(u_, v_, 1ll * w_);
  }

  LL ans = -kInf;
  rt = Find(1);
  for (int i = 0; i <= n; ++ i) ans = std::max(ans, f[rt][i]);
  printf("%lld\n", ans);
  return 0;
}

写在最后

学到了什么:

  • 别急着预处理,可能丢失重要信息。
  • py 里的 / 是实数除法,// 是整除,铭记!

标签:第六场,int,ll,多校,kN,牛客,cnt,include,sum5
From: https://www.cnblogs.com/luckyblock/p/17607860.html

相关文章

  • 2023年多校联训NOIP层测试3+「SFCOI-3」Sadness Fan Club Round 3
    2023年多校联训NOIP层测试3T1数列变换\(10pts\)考虑暴力,发现\(f\)数列进行一次变换\(A\),再进行一次变换\(B\)后,恢复成了原数列;\(f\)数列进行一次变换\(B\),再进行一次变换\(A\)后,也恢复成了原数列。即变换\(A\)可以和变换\(B\)相互抵消。本质是差分是前缀......
  • 【题解】[2023牛客多校] Distance
    题目传送门:[2023牛客多校]Distance题意对于任意两个元素个数相同的set:A、B,每次可以执行以下两种操作之一:将A中的任意元素加一将B中的任意元素加一\(C(A,B)\)含义为将\(A、B\)改变为完全相同的set所需要花费的最小次数;初始给你两个set:\(S、T\),计算\(\sum_{A\subs......
  • 牛客网项目开发学习
    牛客网项目SpringSpringIocInversionofControl控制反转,是一种面向对象编程的设计思想。DependencyInjection依赖注入,是IOC思想的实现方式。IocContainerIoc容器,是实现依赖注入的关键,本质上是一个工厂。SpringMVC三层架构:表现层,业务层,数据访问层。MVC:Model模型......
  • 牛客多校友谊赛
    D-吹_23暑假友谊赛No.2(nowcoder.com)#include<bits/stdc++.h>#defineintlonglong#defineendl'\n'usingnamespacestd;constintN=1e6+50,INF=0x3f3f3f3f;inta[N],dp[N][2];signedmain(){intn;cin>>n;for......
  • 暑假牛客多校第五场 2023-7-31(G、D、H)
    未补完G.GotoPlayMaimaiDX算法:双指针做法:从左到右用两个指针维护一段区间且右指针不断右移,当这个区间满足题目所给的性质,我们取出区间长度,然后再将左指针右移,直到右指针到边界且左指针指到不符合题目的性质的位置结束,期间不断对符合题目性质的区间长度取最小值。code......
  • 2023牛客暑期多校训练营5
    之前落下的每一场比赛都是要补回来的。。。GGotoPlayMaimaiDX题解的想法比较简单,由于找到满足1,2,3出现至少一次,4出现至少k次的最短区间,所以可以双指针,双指针用于这种长度最短,长度越长越容易满足条件的题就很恰当。我没想到双指针,就写的比较麻烦,预处理每个数后一个1,2,3的位置......
  • 2023年多校联训NOIP层测试2
    2023年多校联训NOIP层测试2爆零了T1HDU4786FibonacciTree\(0pts\)@wangyunbiao:不可以,总司令我:不,可以,总司令T2期末考试\(0pts\)T3麻烦的工作\(0pts\)@wangyunbiao:不可以,总司令我:不,可以,总司令T4小X的Galgame\(0pts\)......
  • 【题解】HDOJ 7329 [2023杭电多校] Touhou Red Red Blue
    题目传送门:HDOJ7329[2023杭电多校]TouhouRedRedBlue题意有两个口袋(口袋容量为1,初始均为空),有若干个UFO按顺序来到你的面前,每个UFO有一个颜色(GRB),你可以选择扔掉它或者把它装进口袋中,如果口袋1空着必须装进口袋1;如果口袋都满了,算上面前的UFO共有三个,有如下三种情况:如......
  • 牛客周赛 Round 5
    牛客周赛Round5A-游游的字母变换_牛客周赛Round5(nowcoder.com)#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;signedmain(){ ios::sync_with_stdio(false); cin.tie(nullptr); strings;cin>>s;for(inti=0;i<s.size......
  • 牛客暑假多校 2023 第五场
    目录写在前面GDHCEI写在最后写在前面战犯在此。我宣布二周年这首是神。以下按个人向难度排序。G尺取法,右端点单调右移过程中右移调整左端点,使区间内每种数都至少出现1次,4出现至少\(k\)次即可。///*By:Luckyblock*/#include<cmath>#include<cstdio>#include<......