题目提交处
duang~
A 牛牛与牛妹的RMQ
这个题目看题解看了好久才看懂,听说已经达到了区域银牌的水平,那我肯定不会放过这个题目的, hhh~
给定一个排列数组a,选定k 个下标,每次可以选取两个进行RMQ运算,问所取到值的概率。
首先,我们分母可以确定下来,两个数可以取一样的、有先后顺序,所以就有 k 2 k^2 k2中结果。
接下来去处理分子的情况:
分子是RMQ的结果,由区间去确定RMQ的结果太被动了, 所以换一种思路,用结果去确定区间。
通过RMQ的性质我们可以发现:RMQ(a, b) 取的是区间(a, b)的最大值, 也就是整个区间的极大值,所以先要预处理每一 个极大值的作用域,要确定它的作用域的话,我们就可以想到单调栈算法可以得出来,(如果单调栈不知道的话, 可以去看看官方题解的讲解,我是把我看官方题解没看懂的地方和大家分享发一下。) 还有一个预处理就是建造线段树,去维护区间中的最大值。
分析每一组值的情况:
先将我们可以取的下标排序idx[],
i
d
x
1
、
i
d
x
2
、
i
d
x
3
…
…
i
d
x
n
idx_1、idx_2、idx_3……idx_n
idx1、idx2、idx3……idxn, 会生成答案的情况有 :
①RMQ值为
a
[
i
d
x
i
]
a[idx_i]
a[idxi]的个数。(单调栈预处理作用)
②
R
M
Q
(
i
d
x
i
−
1
,
i
d
x
i
)
RMQ(idx_{i -1}, idx_i)
RMQ(idxi−1,idxi)的值,因为可能会出现可选下标代表值之外的的值。(线段树预处理作用)
那么如何去确定它出现了多少次呢?
pos代表当前的坐标位置,
L
p
o
s
L_{pos}
Lpos代表最左边小于
a
[
p
o
s
]
a[pos]
a[pos]的坐标,
R
p
o
s
R_{pos}
Rpos代表最右边小于
a
[
p
o
s
]
a[pos]
a[pos]的坐标,
[
L
p
o
s
,
p
o
s
]
[L_{pos}, pos]
[Lpos,pos]中取一个数 ,
[
p
o
s
,
R
p
o
s
]
[pos, R_{pos}]
[pos,Rpos] 中取一个数。区间中取到的数必须是可选下标,所以要用树状数组记录一下区间值的情况(比前缀和快一些)。所以结果就是
g
e
t
s
u
m
(
L
[
p
o
s
]
,
p
o
s
)
∗
g
e
t
s
u
m
(
p
o
s
,
R
[
p
o
s
]
)
∗
2
getsum(L[pos], pos) * getsum(pos, R[pos]) * 2
getsum(L[pos],pos)∗getsum(pos,R[pos])∗2 因为中间取了两次,所以要减掉一次,如果pos这个下标不是可用下标就不用减了。
最后约一下分,结果就出来了。
AC 代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
const int N = 100000 + 10, INF = 0x3f3f3f3f;
int n, m, k, tt;
pii stk[N];
int L[N], R[N], a[N], c[N];
vi alls;
map<int, ll> ansMap;
bool vis[N];
struct Node
{
int l, r, id, v;
Node(int l = 0, int r = 0, int id = 0, int v = 0) : l (l), r(r), id(id), v(v) {}
Node operator + (const Node& t) const {
int pos, val;
v >= t.v ? (pos = id, val = v) : (pos = t.id, val = t.v);
return { l, t.r, pos, val };
}
}tr[N << 2];
void push(int u)
{
tr[u] = tr[u << 1] + tr[u << 1 | 1];
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = { l, r, l, a[l] };
else
{
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
push(u);
}
}
Node query(int u, int l, int r)
{
if (tr[u].l == l && tr[u].r == r) return tr[u];
Node& t = tr[u];
int mid = t.l + t.r >> 1;
if (r <= mid) return query(u << 1, l, r);
if (l > mid) return query(u << 1 | 1, l, r);
return query(u << 1, l, mid) + query(u << 1 | 1, mid + 1, r);
}
void add(int x, int k)
{
for (; x <= n; x += x & -x) c[x] += k;
}
ll ask(int x)
{
ll ans = 0;
for (; x; x -= x & -x) ans += c[x];
return ans;
}
ll get_sum(int l, int r)
{
return ask(r) - ask(l - 1);
}
void cal(int pos)
{
if (ansMap.count(a[pos])) return;
ansMap[a[pos]] = get_sum(L[pos], pos) * get_sum(pos, R[pos]) * 2 - vis[pos];
}
ll gcd(ll a, ll b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
_rep(i, 1, n) cin >> a[i];
build(1, 1, n);
a[0] = INF, a[n + 1] = INF - 1;
_rep(i, 0, n + 1)
{
while (tt && stk[tt].first <= a[i])
{
L[stk[tt].second] = stk[tt - 1].second + 1;
R[stk[tt].second] = i - 1;
--tt;
}
stk[++tt] = make_pair(a[i], i);
}
cin >> m;
while (m--)
{
alls.clear(), ansMap.clear();
cin >> k;
_rep(i, 1, k)
{
int x;
cin >> x;
alls.push_back(x);
add(x, 1);
vis[x] = true;
}
sort(alls.begin(), alls.end());
int sz = alls.size();
_for(i, 0, sz)
{
cal(alls[i]);
if (i) cal(query(1, alls[i - 1], alls[i]).id);
}
_for (i, 0, sz)
{
add(alls[i], -1);
vis[alls[i]] = false;
}
for (const auto& p : ansMap)
{
ll son = p.second, mom = 1ll * k * k;
ll d = gcd(son, mom);
cout << p.first << " " << son / d << "/" << mom / d << endl;
}
}
return 0;
}
C 牛牛与字符串border
找出最小循环节,然后根据字母的众数去构造字符串即可。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % 1000000007
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10;
char s[N];
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
int T;
cin >> T;
while (T--)
{
int n, l, d = 0;
cin >> n >> l >> s;
if (2 * l > n && l != n) d = n - l;
else d = gcd(n, l);
string ans;
_for(i, 0, d)
{
int cnt[26] = { 0 };
for (int j = i; j < n; j += d) cnt[s[j] - 'a']++;
int mx = 0, alp = 0;
_for(j, 0, 26) if (cnt[j] > mx) mx = cnt[j], alp = j;
ans.push_back('a' + alp);
}
_for(i, 0, n / d) cout << ans;
_for(i, 0, n % d) cout << ans[i];
cout << endl;
}
return 0;
}
D 牛牛与整除分块
打表找规律题,不要小看题目给你的任意一个条件,说不定就是突破口。
比如集合S的大小总是严格小于
2
N
2 \sqrt N
2N, 就要去尝试一下,尝试之后你会发现前
⌊
N
⌋
\lfloor \sqrt N \rfloor
⌊N⌋个数的整除之后的结果是不一样的,后面
n
−
⌊
N
⌋
n - \lfloor \sqrt N \rfloor
n−⌊N⌋乘出来的结果有些会有重复结果的出现,但是结果都是连续的。
所以综上所述:
a
n
s
=
{
x
x
≤
⌊
N
⌋
2
∗
a
−
n
/
x
+
(
a
!
=
n
/
a
)
)
x
>
⌊
N
⌋
(
a
=
⌊
N
⌋
)
ans = \begin{cases} x & x \leq \lfloor \sqrt N \rfloor\\ 2 * a - n / x + (a != n / a))& x > \lfloor \sqrt N \rfloor \end{cases} (a = \lfloor \sqrt N \rfloor)
ans={x2∗a−n/x+(a!=n/a))x≤⌊N⌋x>⌊N⌋(a=⌊N⌋)
⌊
N
/
x
⌋
\lfloor N/x \rfloor
⌊N/x⌋得出来的结果就是它的倒数位置。
需要注意的是当
a
!
=
n
/
a
a != n / a
a!=n/a时,有
2
∗
a
+
1
2 * a + 1
2∗a+1种结果。样例有提醒。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % MOD
#define ENDL "\n"
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
int T;
cin >> T;
while (T--)
{
int n, x;
cin >> n >> x;
int a = sqrt(n + 0.5);
if (x <= a) cout << x << ENDL;
else cout << (2 * a - n / x + (a != n / a)) << ENDL;
}
return 0;
}
F 牛牛与交换排序
首先可以确定k的值,例如:5 2 1 4 3, 因为1肯定是第一个回到起始点的,所以k就确定了为3。
然后根据k依次检查即可得到答案,使用暴力翻转是会超时的,每次只翻转k个数,对k个数进行翻转操作可以想到使用懒标记的双向队列,虽然STL中有,但是STL会慢,所以是使用数组模拟双向队列的写法。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % 1000000007
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10;
int n;
int a[N], pos[N];
struct deQue
{
int dq[N << 1], hh = N, tt = N - 1;
bool rev;
bool empty()
{
return hh > tt;
}
int size()
{
return tt - hh + 1;
}
int front()
{
return rev ? dq[tt] : dq[hh];
}
int back()
{
return rev ? dq[hh] : dq[tt];
}
void push_front(int x)
{
rev ? dq[++tt] = x : dq[--hh] = x;
}
void push_back(int x)
{
rev ? dq[--hh] = x : dq[++tt] = x;
}
void pop_front()
{
rev ? --tt : ++hh;
}
void pop_back()
{
rev ? ++hh : --tt;
}
void reverse()
{
rev ^= 1;
}
}q;
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
cin >> n;
int s = 0;
_rep(i, 1, n)
{
cin >> a[i];
pos[a[i]] = i;
if (!s && a[i] != i) s = i;
}
if (!s) return puts("yes\n1"), 0;
int k = pos[s] - s + 1;
_rep(i, 1, k) q.push_back(a[i]);
_rep(i, 1, n)
{
if (q.front() != i && q.size() == k) q.reverse();
if (q.front() != i) return puts("no"), 0;
q.pop_front();
if (i + k <= n) q.push_back(a[i + k]);
}
printf("yes\n%d\n", k);
return 0;
}
G 牛牛与比赛颁奖
这个题目模拟的话会超时,因为n的数据点有
1
e
9
1e9
1e9这么大。
所以的话,需要想到的目标就是从题目数入手。
我做这个题目的时候思考方向不对,我的想法是只要确定了每个点被覆盖了几次就行,这是不对的,应该哪一段被覆盖了相同的次数,具体是哪些点不重要,我们要的只是区间长度,然后然每一次的覆盖次数的数组加上区间长度(不要局限于一维,想出多维之间的联系)。
具体处理就是使用差分的思想去维护覆盖次数。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % MOD
#define ENDL "\n"
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10;
int n, m, all[N];
pii seg[N << 1];
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
cin >> n >> m;
int Au = (n + 9) / 10, Ag = (n + 3) / 4, Cu = (n + 1) / 2;
int sz = 0;
_for (i, 0, m)
{
int l, r;
cin >> l >> r;
seg[sz++] = make_pair(l, 1);
seg[sz++] = make_pair(r + 1, -1);
}
sort(seg, seg + sz);
int cnt = 0;
_for(i, 0, sz)
{
cnt += seg[i].second;
if (i + 1 < sz) all[cnt] += seg[i + 1].first - seg[i].first;
}
int g, s, c, sum = 0;
For(i, m, 1)
{
if (sum < Au) g = i;
if (sum < Ag) s = i;
if (sum < Cu) c = i;
sum += all[i];
all[i] += all[i + 1];
}
cout << all[g] << " " << all[s] - all[g] << " " << all[c] - all[s] << ENDL;
return 0;
}
H 牛牛与棋盘
签到题
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10;
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
int n;
cin >> n;
int x = 1;
_for(i, 0, n)
{
_for(j, 0, n) cout << (x = !x);
x = !x;
cout << endl;
}
return 0;
}
I 牛牛的“质因数”
数字都是按照顺序去排列组合的,所以只要取大于等于前一个数的质数构造一个
F
(
i
)
F(i)
F(i),再判断加上这个质数之后是否小于等于n就可以了。
要注意:
- 末尾加质数的时候要注意他的位数。
- 乘结果要不断取模,防止溢出
代码其实很简单,就是线性筛质数+dfs。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % 1000000007
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 4000000 + 10;
ll ans = 0;
int primes[N], cnt, n;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; ++i)
{
if (!st[i]) primes[cnt++] = i;
for (int j = 0; i * primes[j] <= n; ++j)
{
st[i * primes[j]] = true;
if (i % primes[j] == 0) break;
}
}
}
inline ll len(ll x)
{
ll res = 1;
while (x)
{
x /= 10;
res *= 10;
}
return res;
}
void dfs(int cur, ll s, ll sum)
{
if (sum <= n) ans = mod(ans + s);
_for(i, cur, cnt)
{
if (sum * primes[i] > n) return;
dfs(i, mod(mod(s * len(primes[i])) + primes[i]), sum * primes[i]);
}
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
cin >> n;
get_primes(n);
_for(i, 0, cnt) dfs(i, primes[i] * 1ll, primes[i] * 1ll);
cout << ans << endl;
return 0;
}
J 牛牛想要成为hacker
这题也算是里面比较简单的题目了,当时没有仔细去做这个题,后面补题的时候发现好简单,这算是比赛策略的问题了,如何选择一个题目进行深究也是一个问题,所以选择一个合适排行榜目标很重要(解决办法:查看自己学校的或者自己省份的学校进行比较)。
这个题目想让这份代码TLE,那么最简单的就是构造出一组任意三个无法组成三角形的数据,所以可以想到斐波拉契,但是会超过 10 e 9 10e9 10e9,所以超过的就都用1代替。
AC 代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
const int N = 40 + 10, INF = 0x3f3f3f3f;
int n, a[N];
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
a[0] = 1, a[1] = 2;
_rep(i, 2, 40) a[i] = a[i - 1] + a[i - 2];
_rep(i, 1, n)
{
if (i <= 40) cout << a[i] << " ";
else cout << 1 << " ";
}
cout << endl;
return 0;
}