前言
IT WAS NEVER MEANT TO BE.
———Eret & Wilbursoot
暑假的最后一场模拟赛,无论是好是坏,都是一个标志性的结束。
但是结果很差。
T1 \text{T1} T1:签到题(有人写挂了),评分 10 10 10,有 114514 × 1919810 114514\times1919810 114514×1919810 种方法能 AC \text{AC} AC。
T2
\text{T2}
T2:逆康托展开
+
+
+ 压位高精
+
+
+ 数论
+
+
+ 二分查找
+
+
+ 树状数组!!!评分unsigned __int128 score=-1;
。
T3 \text{T3} T3:数论,评分 30 30 30。
T4 \text{T4} T4:二分答案 + + + 并查集 / / / 最短路,很套路,评分 30 30 30。
然后 10 m i n 10min 10min 写完 T1 \text{T1} T1,乱推 T2,T3 \text{T2,T3} T2,T3,写了个 B F S \rm BFS BFS 乱搞 T4 \text{T4} T4 发现搞不了无向边,最后 T2 D F S \text{T2}\,\rm DFS T2DFS 骗 30 30 30, T3 \text{T3} T3 搞了个质数筛 + + + O ( n ) \mathcal{O}(n) O(n) 判断骗 60 60 60 分, T4 \text{T4} T4 交了 B F S \rm BFS BFS 竟然骗了 10 10 10 分。
但是 T4 \text{T4} T4 和几乎刚考过的比赛中的 T3 \text{T3} T3 很像啊!8月10日模拟赛题解-T3
正文
T1 \text{T1} T1 序列
Description \text{Description} Description
有一个长度为 n ( n ≤ 2 × 1 0 5 ) n(n\le2\times10^5) n(n≤2×105) 的序列 a a a 和一个初始为空的序列 b b b,将依次进行 n n n 次操作,其中第 i i i 次操作分为以下两步:
- 将 a i a_i ai 加到序列 b b b 的尾部;
- 翻转序列 b b b。
请求出 n n n 次操作之后的序列 b b b。
Solution \text{Solution} Solution
直接模拟即可,这里使用双端队列,每操作一次就换一个方向插入,最后判断从哪个方向输出。
时间复杂度 O ( n ) \mathcal{O}(n) O(n)。
Code \text{Code} Code
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int a[200005];
deque<int> dq;
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
if (i & 1)
{
dq.push_front(a[i]);
}
else
{
dq.push_back(a[i]);
}
}
if (n & 1)
{
while (!dq.empty())
{
printf("%d ", dq.front());
dq.pop_front();
}
}
else
{
while (!dq.empty())
{
printf("%d ", dq.back());
dq.pop_back();
}
}
return 0;
}
T3 \text{T3} T3 简单数学题
Description \text{Description} Description
对于一个正整数 N ( N ≤ 1 0 14 ) N(N\le10^{14}) N(N≤1014),求出所有的正整数 T T T,使得 N − 1 2 T N − T ∈ N ∗ \dfrac{N-\dfrac{1}{2}T}{N-T}\in\mathbb{N*} N−TN−21T∈N∗。
Solution \text{Solution} Solution
乱推式子,只要能推出来。
设 N − 1 2 T N − T = K \dfrac{N-\dfrac{1}{2}T}{N-T}=K N−TN−21T=K,则 K ∈ N ∗ K\in \mathbb{N*} K∈N∗。
N − 1 2 T N − T = K \dfrac{N-\dfrac{1}{2}T}{N-T}=K N−TN−21T=K
2 N − T 2 N − 2 T = K \dfrac{2N-T}{2N-2T}=K 2N−2T2N−T=K
2 N − T = 2 K N − 2 K T 2N-T=2KN-2KT 2N−T=2KN−2KT
2 K N − 2 N = 2 K T − T 2KN-2N=2KT-T 2KN−2N=2KT−T
( 2 K − 2 ) N = ( 2 K − 1 ) T (2K-2)N=(2K-1)T (2K−2)N=(2K−1)T
2 K − 2 2 K − 1 N = T \dfrac{2K-2}{2K-1}N=T 2K−12K−2N=T
∵ gcd ( 2 K − 2 , 2 K − 1 ) = 1 \because \gcd(2K-2,2K-1)=1 ∵gcd(2K−2,2K−1)=1
∴ ( 2 K − 1 ) \therefore (2K-1) ∴(2K−1) 为 N N N 的因数,且 ( 2 K − 1 ) (2K-1) (2K−1) 是奇数。
∴ ( 2 K − 1 ) \therefore (2K-1) ∴(2K−1) 为 N N N 的奇因数。
那么筛出 N N N 的所有奇因数( 1 1 1 除外),这个是 O ( N ) \mathcal{O}(\sqrt{N}) O(N) 的。
因为筛出来的是打乱顺序的,所以要 sort \operatorname{sort} sort 一遍,这个是 O ( N log N ) \mathcal{O}(N\log N) O(NlogN) 的。
最后 for \operatorname{for} for 一遍输出 X − 1 X N \dfrac{X-1}{X}N XX−1N( X X X 是 N N N 的奇因数) 即可。
时间复杂度 O ( N log N ) \mathcal{O}(N\log N) O(NlogN)。
Code \text{Code} Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int MAXN = 2e7 + 5;
int cnt;
int v[MAXN];
signed main()
{
int n;
scanf("%lld", &n);
if (n == 1)
{
puts("0");
return 0;
}
for (int i = 2; i * i <= n; i++)
{
if (n % i == 0)
{
if (i & 1)
{
v[++cnt] = i;
}
if ((n / i) & 1)
{
v[++cnt] = n / i;
}
}
}
if (n & 1)
{
v[++cnt] = n;
}
printf("%lld ", cnt);
sort(v + 1, v + cnt + 1);
for (int i = 1; i <= cnt; i++)
{
printf("%lld ", n / v[i] * (v[i] - 1));
}
return 0;
}
T4 \text{T4} T4 遨游
Descrition \text{Descrition} Descrition
给定一张有 n n n 个点 m m m 条边无向连通图,第 i i i 条边连接着 u i u_i ui 和 v i v_i vi 有边权 w i ( w i ≤ 15000 ) w_i(w_i\le15000) wi(wi≤15000),要从节点 s s s 走到节点 t t t,请求出一对 L , R ∈ N ∗ L,R\in N* L,R∈N∗,满足
- L ≤ R L\le R L≤R;
- 只在图中保留边权在 [ L , R ] [L,R] [L,R] 范围中的边后,能从 s s s 走到 t t t;
- L L L 要尽可能大, R R R 在满足 L L L 尽量大的基础上尽量小。
Solution1 \text{Solution1} Solution1
显然 L = ⌊ min { w i } ⌋ , R = ⌈ max { w i } ⌉ L=\left\lfloor\min\{w_i\}\right\rfloor,R=\left\lceil\max\{w_i\}\right\rceil L=⌊min{wi}⌋,R=⌈max{wi}⌉。
看到最小值最大
,最大值最小
立马想到二分答案。
先二分 L L L,用并查集维护,将 w i ≥ m i d w_i\ge mid wi≥mid 的所有边 i i i 的 u i u_i ui 和 v i v_i vi并入同一个集合,最后判断 s s s 和 t t t 是否在同一个集合即可。
得到 L L L 后存下来,再二分 R R R,注意要在满足 w i ≥ L w_i\ge L wi≥L 的基础上,所以将 L ≤ w i ≤ m i d L\le w_i\le mid L≤wi≤mid 的所有边 i i i 的 u i u_i ui 和 v i v_i vi 并入同一个集合,然后判断。
时间复杂度为 O ( log 15000 × m × α ( m ) ) ≈ O ( 50 m ) \mathcal{O}(\log 15000\times m\times\alpha(m))\approx \mathcal{O}(50m) O(log15000×m×α(m))≈O(50m)。
Code1 \text{Code1} Code1
#include <iostream>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
const int MAXN = 50005;
const int MAXM = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int n, m, s, t, tot;
int c[MAXN], x[MAXN], fa[MAXN];
struct edge
{
int u, v;
double w;
}e[MAXM << 1];
void init()
{
for (int i = 1; i <= tot; i++)
{
fa[i] = i;
}
}
int find(int x)
{
if (x == fa[x])
{
return x;
}
return fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
x = find(x), y = find(y);
if (x != y)
{
fa[y] = x;
}
}
bool check(int l, int r)
{
init();
for (int i = 1; i <= m; i++)
{
if (l <= e[i].w && e[i].w <= r)
{
merge(e[i].u, e[i].v);
}
}
return find(s) == find(t);
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d%lf", &e[i].u, &e[i].v, &e[i].w);
}
for (int i = 1; i <= n; i++)
{
int t;
scanf("%d", &t);
tot += t;
for (int j = 1; j <= t; j++)
{
int u;
scanf("%d", &u);
c[u] = i;
}
}
for (int i = 1; i <= n; i++)
{
scanf("%d", x + i);
}
for (int i = 1; i <= m; i++)
{
if (c[e[i].u] == c[e[i].v]) // 题目要求,乱搞
{
e[i].w = e[i].w * x[c[e[i].u]] / 100;
}
else
{
e[i].w = e[i].w * (x[c[e[i].u]] + x[c[e[i].v]]) / 200;
}
}
scanf("%d%d", &s, &t);
int l = 0, r = 15000;
while (l < r)
{
int mid = (l + r + 1) >> 1;
if (check(mid, INF))
{
l = mid;
}
else
{
r = mid - 1;
}
}
int ansl = l;
printf("%d ", l);
l = 0, r = 15000;
while (l < r)
{
int mid = (l + r) >> 1;
if (check(ansl, mid))
{
r = mid;
}
else
{
l = mid + 1;
}
}
printf("%d\n", l);
return 0;
}
Solution2 \text{Solution2} Solution2
跑两遍 D i j k s t r a \rm Dijkstra Dijkstra 即可。
时间复杂度 O ( ( m + 15000 ) log 15000 ) ≈ O ( 15 m ) \mathcal{O}((m+15000)\log15000)\approx\mathcal{O}(15m) O((m+15000)log15000)≈O(15m)。
Code2 \text{Code2} Code2
#include <iostream>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
const int MAXN = 50005;
const int MAXM = 1e5 + 5;
int n, m, s, t, cnt, tot, ansl;
int head[MAXN], u[MAXM], v[MAXM], c[MAXN], x[MAXN];
double w[MAXM], dis[MAXN];
bool vis[MAXN];
struct edge
{
int to;
double dis;
int nxt;
}e[MAXM << 1];
void add(int u, int v, double w)
{
e[++cnt] = edge{v, w, head[u]};
head[u] = cnt;
}
struct node_L
{
int from;
double minn;
bool operator <(const node_L &x)const
{
return x.minn > minn;
}
};
void dijkstra_L()
{
priority_queue<node_L> pq;
pq.push(node_L{s, 0x3f3f3f3f});
while (!pq.empty())
{
int u = pq.top().from;
double mi = pq.top().minn;
pq.pop();
if (vis[u])
{
continue;
}
vis[u] = true;
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
double nowm = min(mi, e[i].dis);
if (dis[v] < nowm)
{
dis[v] = nowm;
pq.push(node_L{v, dis[v]});
}
}
}
ansl = dis[t];
}
struct node_R
{
int from;
double maxx;
bool operator <(const node_R &x)const
{
return x.maxx < maxx;
}
};
void dijkstra_R()
{
for (int i = 1; i <= tot; i++)
{
dis[i] = 0x3f3f3f3f;
vis[i] = false;
}
priority_queue<node_R> pq;
pq.push(node_R{s, 0});
dis[s] = 0;
while (!pq.empty())
{
int u = pq.top().from;
double ma = pq.top().maxx;
pq.pop();
if (vis[u])
{
continue;
}
vis[u] = true;
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
double nowm = max(ma, e[i].dis);
if (dis[v] > nowm && e[i].dis >= ansl)
{
dis[v] = nowm;
pq.push(node_R{v, dis[v]});
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d%lf", u + i, v + i, w + i);
}
for (int i = 1; i <= n; i++)
{
int t;
scanf("%d", &t);
tot += t;
for (int j = 1; j <= t; j++)
{
int u;
scanf("%d", &u);
c[u] = i;
}
}
for (int i = 1; i <= n; i++)
{
scanf("%d", x + i);
}
for (int i = 1; i <= m; i++)
{
if (c[u[i]] == c[v[i]])
{
w[i] = w[i] * x[c[u[i]]] / 100;
}
else
{
w[i] = w[i] * (x[c[u[i]]] + x[c[v[i]]]) / 200;
}
add(u[i], v[i], w[i]);
add(v[i], u[i], w[i]);
}
scanf("%d%d", &s, &t);
dijkstra_L();
printf("%d ", (int)(floor(dis[t])));
dijkstra_R();
printf("%d\n", (int)(ceil(dis[t])));
return 0;
}
∗ T2 *\text{T2} ∗T2 今天你 AK \text{AK} AK 了吗?
Description \text{Description} Description
给定 n ( 1 ≤ n ≤ 100000 ) n(1\le n\le100000) n(1≤n≤100000) 和 k ( 1 ≤ k ≤ min ( 1 0 20000 , n ! ) ) k(1\le k\le\min(10^{20000},n!)) k(1≤k≤min(1020000,n!)),求出字典序第 k k k 小的 n n n 的排列。
Solution \text{Solution} Solution
Q:为什么放在了最后还加了个 ∗ * ∗ 号?
A:因为这题太毒瘤了。
然后作者太懒了,所以懒得写了。
Code \text{Code} Code
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;
const int MAXN = 1e5 + 5;
int n, len;
int a[MAXN], mod[MAXN], c[MAXN];
char k[MAXN];
int div(int x)
{
int r = 0;
for (int i = len; i >= 0; i--)
{
int s = r * (int)(1e13) + a[i];
a[i] = s / x;
r = s % x;
}
while (!a[len])
{
len--;
}
return r;
}
int lowbit(int x)
{
return x & -x;
}
void update(int x)
{
for (int i = x; i <= n; i += lowbit(i))
{
c[i]++;
}
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += c[i];
}
return res;
}
signed main()
{
scanf("%lld%s", &n, k);
len = strlen(k);
int pos = 0;
for (int i = len - 1; i >= 0; i--)
{
if ((len - i - 1) % 13 == 0)
{
pos++;
}
mod[i] = pos - 1;
}
for (int i = 0; i < len; i++)
{
a[mod[i]] = a[mod[i]] * 10 + k[i] - 48;
}
a[0]--;
for (int i = 0; i < len; i++)
{
if (a[i] < 0)
{
a[i + 1]--;
a[i] += (int)(1e13);
}
}
len = pos - 1;
for (int i = 1; i <= n; i++)
{
mod[n - i + 1] = div(i);
}
for (int i = 1; i <= n; i++)
{
int l = 1, r = n;
while (l < r)
{
int mid = (l + r) >> 1;
int x = mid - query(mid);
if (x <= mod[i])
{
l = mid + 1;
}
else
{
r = mid;
}
}
update(l);
printf("%lld ", l);
}
return 0;
}