前言
IT WAS NEVER MEANT TO BE.
———Eret & Wilbursoot
暑假的最后一场模拟赛,无论是好是坏,都是一个标志性的结束。
但是结果很差。
\(\text{T1}\):签到题(有人写挂了),评分 \(10\),有 \(114514\times1919810\) 种方法能 \(\text{AC}\)。
\(\text{T2}\):逆康托展开 \(+\) 压位高精 \(+\) 数论 \(+\) 二分查找 \(+\) 树状数组!!!评分unsigned __int128 score=-1;
。
\(\text{T3}\):数论,评分 \(30\)。
\(\text{T4}\):二分答案 \(+\) 并查集 \(/\) 最短路,很套路,评分 \(30\)。
然后 \(10min\) 写完 \(\text{T1}\),乱推 \(\text{T2,T3}\),写了个 \(\rm BFS\) 乱搞 \(\text{T4}\) 发现搞不了无向边,最后 \(\text{T2}\,\rm DFS\) 骗 \(30\),\(\text{T3}\) 搞了个质数筛 \(+\) \(\mathcal{O}(n)\) 判断骗 \(60\) 分,\(\text{T4}\) 交了 \(\rm BFS\) 竟然骗了 \(10\) 分。
但是 \(\text{T4}\) 和几乎刚考过的比赛中的 \(\text{T3}\) 很像啊!8月10日模拟赛题解-T3
正文
\(\text{T1}\) 序列
\(\text{Description}\)
有一个长度为 \(n(n\le2\times10^5)\) 的序列 \(a\) 和一个初始为空的序列 \(b\),将依次进行 \(n\) 次操作,其中第 \(i\) 次操作分为以下两步:
- 将 \(a_i\) 加到序列 \(b\) 的尾部;
- 翻转序列 \(b\)。
请求出 \(n\) 次操作之后的序列 \(b\)。
\(\text{Solution}\)
直接模拟即可,这里使用双端队列,每操作一次就换一个方向插入,最后判断从哪个方向输出。
时间复杂度 \(\mathcal{O}(n)\)。
\(\text{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;
}
\(\text{T3}\) 简单数学题
\(\text{Description}\)
对于一个正整数 \(N(N\le10^{14})\),求出所有的正整数 \(T\),使得 \(\dfrac{N-\dfrac{1}{2}T}{N-T}\in\mathbb{N*}\)。
\(\text{Solution}\)
乱推式子,只要能推出来。
设 \(\dfrac{N-\dfrac{1}{2}T}{N-T}=K\),则 \(K\in \mathbb{N*}\)。
\(\because \gcd(2K-2,2K-1)=1\)
\(\therefore (2K-1)\) 为 \(N\) 的因数,且 \((2K-1)\) 是奇数。
\(\therefore (2K-1)\) 为 \(N\) 的奇因数。
那么筛出 \(N\) 的所有奇因数(\(1\) 除外),这个是 \(\mathcal{O}(\sqrt{N})\) 的。
因为筛出来的是打乱顺序的,所以要 \(\operatorname{sort}\) 一遍,这个是 \(\mathcal{O}(N\log N)\) 的。
最后 \(\operatorname{for}\) 一遍输出 \(\dfrac{X-1}{X}N\)(\(X\) 是 \(N\) 的奇因数) 即可。
时间复杂度 \(\mathcal{O}(N\log N)\)。
\(\text{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;
}
\(\text{T4}\) 遨游
\(\text{Descrition}\)
给定一张有 \(n\) 个点 \(m\) 条边无向连通图,第 \(i\) 条边连接着 \(u_i\) 和 \(v_i\) 有边权 \(w_i(w_i\le15000)\),要从节点 \(s\) 走到节点 \(t\),请求出一对 \(L,R\in N*\),满足
- \(L\le R\);
- 只在图中保留边权在 \([L,R]\) 范围中的边后,能从 \(s\) 走到 \(t\);
- \(L\) 要尽可能大,\(R\) 在满足 \(L\) 尽量大的基础上尽量小。
\(\text{Solution1}\)
显然 \(L=\left\lfloor\min\{w_i\}\right\rfloor,R=\left\lceil\max\{w_i\}\right\rceil\)。
看到最小值最大
,最大值最小
立马想到二分答案。
先二分 \(L\),用并查集维护,将 \(w_i\ge mid\) 的所有边 \(i\) 的 \(u_i\) 和 \(v_i\)并入同一个集合,最后判断 \(s\) 和 \(t\) 是否在同一个集合即可。
得到 \(L\) 后存下来,再二分 \(R\),注意要在满足 \(w_i\ge L\) 的基础上,所以将 \(L\le w_i\le mid\) 的所有边 \(i\) 的 \(u_i\) 和 \(v_i\) 并入同一个集合,然后判断。
时间复杂度为 \(\mathcal{O}(\log 15000\times m\times\alpha(m))\approx \mathcal{O}(50m)\)。
\(\text{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;
}
\(\text{Solution2}\)
跑两遍 \(\rm Dijkstra\) 即可。
时间复杂度 \(\mathcal{O}((m+15000)\log15000)\approx\mathcal{O}(15m)\)。
\(\text{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;
}
\(*\text{T2}\) 今天你 \(\text{AK}\) 了吗?
\(\text{Description}\)
给定 \(n(1\le n\le100000)\) 和 \(k(1\le k\le\min(10^{20000},n!))\),求出字典序第 \(k\) 小的 \(n\) 的排列。
\(\text{Solution}\)
Q:为什么放在了最后还加了个 \(*\) 号?
A:因为这题太毒瘤了。
然后作者太懒了,所以懒得写了。
\(\text{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;
}