牛牛与整除分块
时
化简可得
于是以为界,在左边或者右边找对应的位置即可。
#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
int main() {
ll T, n, x;
sc(T);
while (T--) {
sc(n), sc(x);
ll a = sqrt(n);
if (x <= a)
pr(x);
else {
ll l = n / a;
ll ans = 0;
if (a != l) ++ans;
ll now = n / x;
ans += a - now + a;
pr(ans);
}
}
return 0;
} 牛牛与交换排序
简单分析后可知
- 第一次操作必然使最小的、不在其位的数让它归位
- 那么长度就已经固定了
- 那么接下来要做的就是模拟,看能否完成排序即可
可以用deque双端队列模拟,也可以用平衡树
#include <bits/stdc++.h>
#define sc(x) scanf("%d", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
int n, a[N];
deque<int> q;
int main() {
sc(n);
rep(i, 1, n) sc(a[i]);
int x = 0; //第一个不在正确位置上的数
// aka 不在正确位置上的 最小的数
rep(i, 1, n) if (a[i] != i) {
x = i;
break;
}
if (!x) return puts("yes\n1"), 0; //当前已经有序
int now, len;
rep(i, x + 1, n) if (a[i] == x) {
now = i; // 这个最小的错位数 现在在now这个位置
len = i - x + 1; // 需要翻转的线段长度
break;
}
rep(i, x, now - 1) q.emplace_front(a[i]);
bool tag = 0;
for (int i = x; i + len - 1 <= n; i++) {
if (!tag)
q.emplace_front(a[i + len - 1]);
else
q.emplace_back(a[i + len - 1]);
if (tag) {
if (q.front() != i) tag = 0;
} else {
if (q.back() != i) tag = 1;
}
if (tag) {
if (q.front() != i) return puts("no"), 0;
q.pop_front();
} else {
if (q.back() != i) return puts("no"), 0;
q.pop_back();
}
}
for (int i = n - len + 2; i <= n; i++) {
if (tag) {
if (q.front() != i) return puts("no"), 0;
q.pop_front();
} else {
if (q.back() != i) return puts("no"), 0;
q.pop_back();
}
}
printf("yes\n%d", len);
return 0;
} 牛牛与比赛颁奖
本题其实是一道非常基础的离散化+差分的板子题。
#include <bits/stdc++.h>
#define sc(x) scanf("%d", &(x))
#define pr(x) printf("%d ", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
typedef pair<int, int> pii;
pii a[N];
vector<int> ori; // 存储原值
unordered_map<int, int> pos; // key:原值 val: 离散化后
int d[N * 2]; // 差分数组
int num[N]; // num[x]表示过x题的队伍数目
int main() {
int n, m;
sc(n), sc(m);
rep(i, 1, m) sc(a[i].first), sc(a[i].second);
// 离散化
rep(i, 1, m) {
ori.emplace_back(a[i].first);
ori.emplace_back(a[i].second + 1);
}
sort(ori.begin(), ori.end());
ori.erase(unique(ori.begin(), ori.end()), ori.end());
int sz = ori.size();
for (int i = 0; i < sz; ++i) pos[ori[i]] = i;
// 差分
rep(i, 1, m) {
d[pos[a[i].first]]++;
d[pos[a[i].second + 1]]--;
}
// 前缀和
for (int i = 1; i < sz; ++i) d[i] += d[i - 1];
// 统计过x题的人数
--sz;
int maxAcNum = 0;
for (int i = 0; i < sz; ++i) {
int acNum = d[i]; //该区间过题数目
maxAcNum = max(maxAcNum, acNum); //更新最多过题数
int teamNum = ori[i + 1] - ori[i]; //该区间队伍数目
num[acNum] += teamNum;
}
// 计算名额
int au = (n + 10 - 1) / 10;
int ag = (n + 4 - 1) / 4;
int cu = (n + 2 - 1) / 2;
// 计算答案
int ans1 = 0, ans2 = 0, ans3 = 0;
for (int i = maxAcNum; i; --i) // 要求至少过1题
if (ans1 < au)
ans1 += num[i];
else if (ans1 + ans2 < ag)
ans2 += num[i];
else if (ans1 + ans2 + ans3 < cu)
ans3 += num[i];
cout << ans1 << ' ' << ans2 << ' ' << ans3;
return 0;
} 更优雅的做法是直接查询差分的map,这样就省去了离散化的过程,码量很小。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int g[N];
map<int, int> mp;
int num[5];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1, x, y; i <= m; ++i) {
scanf("%d%d", &x, &y);
mp[x]++, mp[y + 1]--;
}
num[1] = (n + 10 - 1) / 10;
num[2] = (n + 4 - 1) / 4;
num[3] = (n + 2 - 1) / 2;
int now = 0, pre = 1, maxn = 0;
for (auto &i : mp) { // 遍历差分map
maxn = max(maxn, now);
g[now] += i.first - pre; // 前后位置差值就是这个区间的人数
now += i.second; // 加上差分量 差分量就是过题数的位移量
pre = i.first;
}
int ans1 = 0, ans2 = 0, ans3 = 0;
for (int i = maxn; i; --i) // 要求至少过1题
if (ans1 < num[1])
ans1 += g[i];
else if (ans1 + ans2 < num[2])
ans2 += g[i];
else if (ans1 + ans2 + ans3 < num[3])
ans3 += g[i];
cout << ans1 << ' ' << ans2 << ' ' << ans3;
return 0;
} 牛牛的“质因数”
埃筛做法
首先什么是埃筛:
#include <stdio.h>
#include <string.h>
const int N = 100 + 8;
int isPrime[N];
void sieve() {
memset(isPrime, -1, sizeof isPrime);
isPrime[0] = isPrime[1] = 0;
for (int i = 2; i < N; ++i)
if (isPrime[i])
for (int j = 2; i * j < N; ++j) isPrime[i * j] = 0;
}
int main() {
sieve();
for (int i = 2; i < N; ++i)
if (isPrime[i]) printf("%d ", i);
return 0;
} 可以发现,在朴素的埃筛里,对于每一个合数,它会被每个它的质因数筛到。而如果是质数,他的质因数合并就是它本身。
所以只需要实时更新下每个数的质因数合并即可。
#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = l; i <= r; ++i)
using namespace std;
typedef long long ll;
const int N = 4e6 + 7;
const int mod = 1e9 + 7;
bitset<N> isPrime;
ll ans[N];
int main() {
ll w[10] = {1};
rep(i, 1, 9) w[i] = w[i - 1] * 10;
ll n;
sc(n);
isPrime.set();
rep(i, 2, n) {
if (isPrime[i]) {
ans[i] = i;
int len = log10(i) + 1;
for (int j = 2; i * j <= n; ++j) {
int now = i * j;
isPrime[now] = 0;
do {
ans[i * j] = (ans[i * j] * w[len] % mod + i) % mod;
now /= i;
} while (now % i == 0);
}
}
}
ll res = 0;
rep(i, 2, n) res = (res + ans[i]) % mod;
pr(res);
return 0;
} 这种做法时间复杂度,本题300ms
欧拉筛 + DFS
欧拉筛是近似线性的筛法,这意味着不能像埃筛一样,对每个数更新答案。
但是可以从另一个角度出发:
- 对于每个质数,它的质因数合并就是它本身。
- 对于每个合数,它的质因数合并是它所有质因数的拼接。
- 反过来说,可以用所有的质因数乘出来所有的数,对于质因数合并,也就是拼接。
- 所以每个数和它的质因数合并都是一一对应的。
所以,可以直接去尝试拼接就可以了,如果是在所求范围内的,就加上权重即可,这也是唯一分解定理的逆向应用,非常巧妙。
#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = l; i <= r; ++i)
using namespace std;
typedef long long ll;
const int N = 4e6 + 7;
const int mod = 1e9 + 7;
bitset<N> isPrime;
int prim[N + 10];
int pn = 0;
ll ans, n;
int w[10] = {1};
int pw[N];
void pre(int N) {
rep(i, 1, 9) w[i] = w[i - 1] * 10;
isPrime.set();
for (int i = 2; i <= N; i++) {
if (isPrime[i]) pw[pn] = w[int(log10(i) + 1)], prim[pn++] = i;
for (int j = 0; j < pn && i * prim[j] <= N; ++j) {
isPrime[i * prim[j]] = 0;
if (i % prim[j] == 0) break;
}
}
}
void dfs(int pos, ll key, ll val) {
ans = (ans + val) % mod;
for (int i = pos; i < pn; ++i) { // merge with bigger prime num
if (key * prim[i] > n) return;
dfs(i, key * prim[i], (val * pw[i] % mod + prim[i]) % mod);
}
}
int main() {
sc(n);
pre(n);
for (int i = 0; i < pn && prim[i] <= n; ++i)
// start dfs from each prime num
dfs(i, prim[i], prim[i]);
pr(ans);
return 0;
} 这种做法时间复杂度,本题70ms
牛牛想要成为hacker
本题很容易想到用斐波那契数列构建,但是fib很快就会超过1e9,数据项并不够。
所以:
- 不可能找不到三角形,只能推迟,但无法阻止
- 时间复杂度提示
>>> from math import log2 >>> log2(100000) 16.609640474436812
比赛的时候我想到了这里,但当时以为就是要让i循环走到16之后即可,所构建的数据也让i循环走到了21。但其实这是不够的:因为对于每个i循环,k并没有走次。
所以为了让次数尽可能多,必须降序构建。
最优的构造应当是降序的fib序列。
fib = [1, 1]
while fib[-1]+fib[-2] < 1e9:
fib.append(fib[-1]+fib[-2])
fib.reverse()
n = int(input())
sz = len(fib)
for i in range(n):
print(fib[i] if i < sz else 1, end=' ')
这也是一种比较有趣的写法
#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld ", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
int main() {
int n;
scanf("%d", &n);
int x = 1 << 29;
for(int i = 1;i <= n;i++){
printf("%d ", x);
if(x > 1) x >>= 1;
}
return 0;
} 
京公网安备 11010502036488号