8月27日模拟赛题解

前言

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(n2×105) 的序列 a a a 和一个初始为空的序列 b b b,将依次进行 n n n 次操作,其中第 i i i 次操作分为以下两步:

  1. a i a_i ai 加到序列 b b b 的尾部;
  2. 翻转序列 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(N1014),求出所有的正整数 T T T,使得 N − 1 2 T N − T ∈ N ∗ \dfrac{N-\dfrac{1}{2}T}{N-T}\in\mathbb{N*} NTN21TN

Solution \text{Solution} Solution

乱推式子,只要能推出来。

N − 1 2 T N − T = K \dfrac{N-\dfrac{1}{2}T}{N-T}=K NTN21T=K,则 K ∈ N ∗ K\in \mathbb{N*} KN

N − 1 2 T N − T = K \dfrac{N-\dfrac{1}{2}T}{N-T}=K NTN21T=K

2 N − T 2 N − 2 T = K \dfrac{2N-T}{2N-2T}=K 2N2T2NT=K

2 N − T = 2 K N − 2 K T 2N-T=2KN-2KT 2NT=2KN2KT

2 K N − 2 N = 2 K T − T 2KN-2N=2KT-T 2KN2N=2KTT

( 2 K − 2 ) N = ( 2 K − 1 ) T (2K-2)N=(2K-1)T (2K2)N=(2K1)T

2 K − 2 2 K − 1 N = T \dfrac{2K-2}{2K-1}N=T 2K12K2N=T

∵ gcd ⁡ ( 2 K − 2 , 2 K − 1 ) = 1 \because \gcd(2K-2,2K-1)=1 gcd(2K2,2K1)=1

∴ ( 2 K − 1 ) \therefore (2K-1) (2K1) N N N 的因数,且 ( 2 K − 1 ) (2K-1) (2K1) 是奇数。

∴ ( 2 K − 1 ) \therefore (2K-1) (2K1) 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 XX1N 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(wi15000),要从节点 s s s 走到节点 t t t,请求出一对 L , R ∈ N ∗ L,R\in N* L,RN,满足

  1. L ≤ R L\le R LR
  2. 只在图中保留边权在 [ L , R ] [L,R] [L,R] 范围中的边后,能从 s s s 走到 t t t
  3. 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 wimid 的所有边 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 wiL 的基础上,所以将 L ≤ w i ≤ m i d L\le w_i\le mid Lwimid 的所有边 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(1n100000) k ( 1 ≤ k ≤ min ⁡ ( 1 0 20000 , n ! ) ) k(1\le k\le\min(10^{20000},n!)) k(1kmin(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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值