Codeforces Round #686 (Div. 3) 11/24

比赛链接

T1

构造题,T组,输出一个长度为n的全排列,并且保证全排列中没有 p i = i p_i=i pi=i的点。
n ≤ 100 n\leq 100 n100

输出2 3 4 5… n 1,即可。

T2

T组输入,每次输入长度为n的数组,现在需要你找到出现一次的数里面最小的是对应下标在什么位置,如果没有数出现一次那么就输出-1。
n ≤ 2 ∗ 1 0 5 n\leq2*10^5 n2105

开个计数器模拟一下就行了。

T3

给你长度为n的序列,以及其中的元素值,你可以选定一个x把原数组根据 a i = x a_i=x ai=x,切割成一些小块。问这些小块最少的数量是多少。
n ≤ 2 ∗ 1 0 5 , a i ≤ n n\leq2*10^5,a_i\leq n n2105ain

发现没遇到一个数和后面一个数不同,说明当前数一定是分割点。那么遍历过去找到以哪个答案为x会是最佳答案就行了。

#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define all(__vv__) (__vv__).begin(), (__vv__).end()
#define endl "\n"
#define pai pair<int, int>
#define ms(__x__,__val__) memset(__x__, __val__, sizeof(__x__))
typedef long long ll; typedef unsigned long long ull; typedef long double ld;
inline ll read() { ll s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar())	s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; }
inline void print(ll x, int op = 10) { if (!x) { putchar('0'); if (op)	putchar(op); return; }	char F[40]; ll tmp = x > 0 ? x : -x;	if (x < 0)putchar('-');	int cnt = 0;	while (tmp > 0) { F[cnt++] = tmp % 10 + '0';		tmp /= 10; }	while (cnt > 0)putchar(F[--cnt]);	if (op)	putchar(op); }
inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
ll qpow(ll a, ll b) { ll ans = 1;	while (b) { if (b & 1)	ans *= a;		b >>= 1;		a *= a; }	return ans; }	ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; }
const int dir[][2] = { {0,1},{1,0},{0,-1},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1} };
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 7;

int n, m;
int a[N], c[N];
void solve() {
	n = read();
	for (int i = 1; i <= n; ++i)	a[i] = read(), c[i] = 1;
	for (int i = 1; i <= n; ++i) {
		if (a[i] != a[i + 1] and i != n)
			++c[a[i]];
	}
	--c[a[1]];
	int ans = INF;
	for (int i = 1; i <= n; ++i)
		ans = min(ans, c[a[i]]);
	print(ans);
}

int main() {
	int T = read();	while (T--)
		solve();
	return 0;
}

T4

构造题,T组输入,每组输入一个n,你可以构造一个尽可能长的序列使得你构造出来的序列满足下面几个要求。

  1. ∏ a i = n \prod a_i=n ai=n
  2. 并且使得任取一个下标 i i i,都可以整除下标比它小的位置上的所有元素

n ≤ 1 0 10 n\leq10^{10} n1010

根据唯一分解定理,分解质因数,我们按照幂次最高的素数构造成的序列就是最长的。又因为一个数的素数因子一定 n \sqrt n n 范围内筛选,所以可以把题目范围压缩在 1 0 5 10^5 105,就可以进行素数筛法了。

const int N = 1e5 + 7;

ll n, m;
int prime[N], cnt;
bool vis[N];
void getprime() {
	vis[1] = 1;
	rep(i, 2, N - 1) {
		if (!vis[i])	prime[++cnt] = i;
		rep(j, 1, cnt) {
			if (i * prime[j] >= N)	break;
			vis[i * prime[j]] = 1;
			if (i % prime[j] == 0)	break;
		}
	}
}

auto calc(ll x) {
	unordered_map<ll, int> mp;
	rep(i, 1, cnt) {
		if (prime[i] > x)	break;
		while (x % prime[i] == 0)
			++mp[prime[i]], x /= prime[i];
	}
	if (x != 1)	++mp[x];
	return mp;
}

void solve() {
	n = read();
	unordered_map<ll, int> mp = calc(n);
	int ans = 0;
	for (auto& it : mp)
		if (it.second > ans)
			ans = it.second, m = it.first;
	print(ans);
	rep(i, 1, ans - 1)	print(m, 32);
	print(n / qpow(m, ans - 1));
}

T5

T组输入,给你n个节点还有n条边构成的一个无向连通图,没有自环没有重边。我们把这个叫做基环树。现在询问这个基环树上有几条简单路径。
n ≤ 2 ∗ 1 0 5 n\leq2*10^5 n2105

我们可以知道基环树只包含了一个环。那么我们单独举个简单例子来看,如果选择的两个点之间需要经过两个环上的点,那么就一定存在两条简单路径。简单来看就是一个走环的上面一个走环的下面。那么我们可以知道n个节点任选2个答案是 C n 2 = n ∗ ( n − 1 ) 2 C^2_n=\frac{n*(n-1)}{2} Cn2=2n(n1),那么如果我们假设这些路径都存在两条,再减掉只存在一条路径的点对就是我们要找的答案了。那么什么情况的点之间会只有一条路径,就是以环缩点为根来看其余子树上的点之间,就是只有一条简单路径的情况。那么这个可以使用拓扑排序去找不在环上全部的点,再把这些点按照并查集维护一下就可以知道子树的大小了。

const int N = 2e5 + 7;

ll n, m;
int fa[N], sz[N];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void merge(int u, int v) {
	int fu = find(u), fv = find(v);
	if (fu != fv) {
		fa[fv] = fu;
		sz[fu] += sz[fv];
	}
}
vector<int> edge[N];
int deg[N];

void solve() {
	n = read();
	rep(i, 1, n)
		deg[i] = 0, fa[i] = i, sz[i] = 1, edge[i].clear();
	rep(i, 1, n) {
		int u = read(), v = read();
		edge[u].push_back(v);
		edge[v].push_back(u);
		++deg[u], ++deg[v];
	}
	queue<int> q;
	rep(i, 1, n)
		if (deg[i] == 1)	q.push(i);
	while (q.size()) {
		int u = q.front();	q.pop();
		for (auto& v : edge[u]) {
			--deg[v];
			merge(u, v);
			if (deg[v] == 1)	q.push(v);
		}
	}
	ll ans = n * (n - 1); // 假设全在环上
	rep(i, 1, n) {
		if (fa[i] == i)
			ans -= 1ll * sz[i] * (sz[i] - 1) / 2;
	}
	print(ans);
}

T6

给出长度为n的序列,现在要求你把序列分成三段。并且第一段的最大值等于第二段的最小值等于第三段的最大值。输出分割点。如果不存在分割点的话输出一个NO。
n ≤ 2 ∗ 1 0 5 , a i ≤ 1 0 9 n\leq2*10^5,a_i\leq10^9 n2105,ai109

我们首先观察题目,发现我们只存在查找操作并没有修改操作,我们可以改变的只是挑选的区间范围,那静态的区间RMQ问题,比较简单的解决办法就是ST表。那么我们可以使用ST表O(1)查询出我们要的区间答案,但是我们要将区间分成三段必有2个分割点在中间位置,如果我们暴力的枚举时间复杂度不允许。那我们再看看能不能优化一下,我们发现对于枚举的第二个变量来看,把它往后移一个单位,中间部分的最小值只可能变小不可能变大的。第三部分的最大值只可能变大不可能变小。那么我们就发现了第二三部分的单调性,也就是我们不需要使用枚举办法去找第二个分割点,使用二分去找就行了。第一个分割点我们就只能枚举过去了。

  1. 如果第二个区间的最小值比第一个区间的最大值都要更小,那么我们继续往后找必不可能找到符合要求的第二部分的最小值等于第一部分的最大值,所以我们要把第二个区间范围缩小,既r=mid-1。
  2. 如果第二个区间的最小值比第一个区间的最大值都要大,说明我们要把第二个区间范围扩大,寻找更小的最小值。既l=mid+1。
  3. 如果第二个区间的最小值等于了第一个区间的最大值,我们还要看第三个区间的最大值和第一个区间的最大值的大小关系,如果第三个区间的最大值比第一个区间更大,说明我们要让第三个区间缩小一点,尝试减小区间的最大值。既l=mid+1。
  4. 如果第三个区间的最大值小于第一个区间的最大值,我们就要让第三个区间变大,使得第三个区间的最大值变大一点。既r=mid-1。
  5. 不然就是找到要求了,输出我们枚举的第一个分割点i,以及第二个分割点mid。
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define all(__vv__) (__vv__).begin(), (__vv__).end()
#define endl "\n"
#define pai pair<int, int>
#define ms(__x__,__val__) memset(__x__, __val__, sizeof(__x__))
typedef long long ll; typedef unsigned long long ull; typedef long double ld;
inline ll read() { ll s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar())	s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; }
inline void print(ll x, int op = 10) { if (!x) { putchar('0'); if (op)	putchar(op); return; }	char F[40]; ll tmp = x > 0 ? x : -x;	if (x < 0)putchar('-');	int cnt = 0;	while (tmp > 0) { F[cnt++] = tmp % 10 + '0';		tmp /= 10; }	while (cnt > 0)putchar(F[--cnt]);	if (op)	putchar(op); }
inline ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }
ll qpow(ll a, ll b) { ll ans = 1;	while (b) { if (b & 1)	ans *= a;		b >>= 1;		a *= a; }	return ans; }	ll qpow(ll a, ll b, ll mod) { ll ans = 1; while (b) { if (b & 1)(ans *= a) %= mod; b >>= 1; (a *= a) %= mod; }return ans % mod; }
const int dir[][2] = { {0,1},{1,0},{0,-1},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1} };
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 7;

int st[2][N][21], Log2[N];

void pre() {
	Log2[1] = 0;
	Log2[2] = 1;
	for (int i = 3; i < N; ++i)	Log2[i] = Log2[i >> 1] + 1;
}
int query_min(int l, int r) {
	int s = Log2[r - l + 1];
	return min(st[0][l][s], st[0][r - (1 << s) + 1][s]);
}
int query_max(int l, int r) {
	int s = Log2[r - l + 1];
	return max(st[1][l][s], st[1][r - (1 << s) + 1][s]);
}

void solve() {
	int n = read();
	for (int i = 1; i <= n; ++i)
		st[0][i][0] = st[1][i][0] = read();
	int t = Log2[n]; //区间最长的2次方
	for (int j = 1; j <= t; ++j)
		for (int i = 1; i + (1 << j) - 1 <= n; ++i)
			st[0][i][j] = min(st[0][i][j - 1], st[0][i + (1 << (j - 1))][j - 1]);
	for (int j = 1; j <= t; ++j)
		for (int i = 1; i + (1 << j) - 1 <= n; ++i)
			st[1][i][j] = max(st[1][i][j - 1], st[1][i + (1 << (j - 1))][j - 1]);
	for (int i = 1; i <= n - 2; ++i) {
		int max1 = query_max(1, i);
		int l = i + 1, r = n - 1; // 二分模板要备好,注意只使用一套即可,我的写法是[l,r]的
		while (l <= r) {
			int mid = l + r >> 1;
			int min2 = query_min(i + 1, mid);
			if (min2 > max1)	l = mid + 1;
			else if (min2 < max1)	r = mid - 1;
			else {
				int max3 = query_max(mid + 1, n);
				if (max3 > max1)	l = mid + 1;
				else if (max3 < max1)	r = mid - 1;
				else {
					puts("YES");
					printf("%d %d %d\n", i, mid - i, n - mid);
					return;
				}
			}
		}
	}
	puts("NO");
}

int main() {
	pre();
	int T = 1;
	T = read();
	while (T--) {
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值