好长时间没打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}
2−40(存在绝对众数,每次选择到的概率为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)));
}
}