codeforces div716 C、D

好长时间没打cf,一打就掉分(>_<)
比赛地址

C.从[1, n - 1]中选出最多的数,使其乘积模n后为1。

这次主要是C题的结论卡了很长时间,正确做法应该是打表找规律(存在n^2的dp)。可以发现被选择的都是与n互素的数(除了部分数)。

先扔张图:
在这里插入图片描述
从另一个结论的角度来看这个问题:最简剩余系乘法封闭(最简剩余系是与n互素的剩余系,有phi(n)个)。
这里就直接一个一个往里面扔,出现为1就把答案更新一下。

对上面那个结论,需要考虑n - 1在不在数组中。
(代码来自cf id: HolyK)


#include <bits/stdc++.h>
#define perr(a...) fprintf(stderr, a)
#define dbg(a...) perr("\033[32;1m"), perr(a), perr("\033[0m")
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}
 
using LL = long long;
using PII = std::pair<int, int>;
 
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  int n;
  std::cin >> n;
  std::vector<int> a;
  int ans = 0;
  for (int i = 1, r = 1; i < n; i++) {
    if (std::gcd(i, n) > 1) continue;
    r = 1LL * r * i % n;
    a.push_back(i);
    if (r == 1) ans = a.size();
  }
  std::cout << ans << "\n";
  for (int i = 0; i < ans; i++) std::cout << a[i] << " ";
  return 0;
}

D.有一个序列,每次选出 [ l , r ] [l, r] [l,r]区间,可以对这段区间进行划分,使得区间中每段出现最多次数的数,出现次数不超过 ( 段 长 度 + 1 ) / 2 (段长度 + 1)/2 (+1)/2,求每段区间最少划分为几段。

这道题,说实话,我连暴力最开始都没想清楚怎么写是最优的。首先,先明确一下,暴力的做法。
一种比较贪心的做法,首先看能不能一段直接解决。如果出现不能解决的情况,那么就会出现某个值 x x x,数量超过一半。那么一种比较自然的想法,就是把所有的非 x x x的数,都去尽量和 x x x匹配,最后情况大概是非 x x x(记为 y y y)的为 c c c个, x x x c + 1 c +1 c+1个, 剩下的 x x x全部自己分别成段。为什么这么做一定会是最优的呢?首先,一段中, x x x一定比 y y y多一个,否则就能和其他的段合并。那么无论这么分,段数就是 x x x y y y多出来的数目。

现在的问题就是怎么去处理一个区间内最多的数,有没有超过一半。
这里引入绝对众数的概念–超过一半数量的众数。
那么,对序列中任意两个不同的数就相互消去,剩下来的,如果存在绝对众数,则一定是绝对众数。
O ( n l o g n ) O(nlogn) O(nlogn)
于是:

#include<bits/stdc++.h>
#define For(aa, bb, cc) for(int aa = (bb); aa <= (int)(cc); ++aa)
using namespace std;
const int maxn = 3e5 + 10;
int n, q;
int a[maxn]; 
vector<int> p[maxn];
 
#define lr (u << 1)
#define rr (u << 1 | 1)
#define mid ((l + r) >> 1)
 
struct tree{
	int val, cnt;
}tr[maxn << 3];
 
tree merge(tree x, tree y){
	if(x.val == y.val) return (tree){x.val, x.cnt + y.cnt};
	else return x.cnt > y.cnt ? (tree){x.val, x.cnt - y.cnt} : (tree){y.val, y.cnt - x.cnt};
}
 
inline void push_up(int u){
	tr[u] = merge(tr[lr], tr[rr]);
}
 
void build(int u, int l, int r){
	if(l == r){
		tr[u] = (tree){a[l], 1};
		return ;
	}
	build(lr, l, mid);
	build(rr, mid + 1, r);
	push_up(u);
}
 
tree query(int u, int l, int r, int L, int R){
	if(L <= l && r <= R) return tr[u];
	if(R <= mid) return query(lr, l, mid, L, R);
	else if(L > mid) return query(rr, mid + 1, r, L, R);
	else return merge(query(lr, l, mid, L, mid), query(rr, mid + 1, r, mid + 1, R));
}
 
#undef lr
#undef rr
#undef mid
 
int main(){
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	cin >> n >> q;
	For(i, 1, n) cin >> a[i], p[a[i]].push_back(i);
	build(1, 1, n);
	while(q--){
		int l, r;
		cin >> l >> r;
		int x = query(1, 1, n, l, r).val;
		int c = upper_bound(p[x].begin(), p[x].end(), r) - lower_bound(p[x].begin(), p[x].end(), l);
		int ans = max(1, 2 * c - (r - l + 1));
		printf("%d\n", ans);
	}
	return 0;
}

当然,这题很明显是可以莫队来做的,但是维护最大值的复杂度可能会要带个 l o g 2 n log_2{n} log2n,可能会T。
这里出题人给出了两个方法:
一个随机化的方法: O ( n l o g n ) O(nlogn) O(nlogn)
对于每个 [ l , r ] [l,r] [l,r],随机从 [ l , r ] [l,r] [l,r]这段区间选出40个位置,作为可能的绝对众数,那么存在绝对众数但是没有选到的概率约为 2 − 40 2^{-40} 240(存在绝对众数,每次选择到的概率为0.5),错误率很低。

#include <bits/stdc++.h>
using namespace std;
int a[300005];
vector<int> v[300005];
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        v[a[i]].push_back(i);
    }
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
    while (q--)
    {
        int l,r,ans=1;
        scanf("%d%d",&l,&r);
        for (int _=0;_<40;_++)
        {
            int c=a[uniform_int_distribution<int>(l,r)(rng)],f=upper_bound(v[c].begin(),v[c].end(),r)-lower_bound(v[c].begin(),v[c].end(),l);
            ans=max(ans,2*f-(r-l+1));
        }
        printf("%d\n",ans);
    }
}
 

以及一个求区间求最值的方法 O ( n l o g n 2 ) O(nl{ogn}^2) O(nlogn2)
对于线段树中区间查询时每个对应节点的最多次数点,都进行计算,然后选出其中最大的(区间最大一定是局部最大)。

#include <bits/stdc++.h>
using namespace std;
int a[300005],tree[1200005];
vector<int> v[300005];
int cnt(int l,int r,int c)
{
    return upper_bound(v[c].begin(),v[c].end(),r)-lower_bound(v[c].begin(),v[c].end(),l);
}
void build(int node,int st,int en)
{
    if (st==en)
    tree[node]=a[st];
    else
    {
        int mid=(st+en)/2;
        build(2*node,st,mid);
        build(2*node+1,mid+1,en);
        tree[node]=(cnt(st,en,tree[2*node])>cnt(st,en,tree[2*node+1])? tree[2*node]:tree[2*node+1]);
    }
}
int query(int node,int st,int en,int l,int r)
{
    if (en<l || st>r || r<l)
    return 0;
    if (l<=st && en<=r)
    return cnt(l,r,tree[node]);
    int mid=(st+en)/2;
    return max(query(2*node,st,mid,l,r),query(2*node+1,mid+1,en,l,r));
}
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        v[a[i]].push_back(i);
    }
    build(1,1,n);
    while (q--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",max(1,2*query(1,1,n,l,r)-(r-l+1)));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值