Codeforces Round #696 (Div. 2)

比赛地址

A(水题)

题目链接

题目:
给出一个01串,现在要求求得另一个相同长度01串,使得两个串相加(允许为2),去除连续相同子序列后的三进制数最大

解析:
首先长度优先级肯定大于高位数字大小的优先级,所以要保证不被去除任何连续相同子序列的情况下,让高位数尽可能地大,于是可以考虑贪心的加1(即如果和上一位相加的结果相同则加0)

#include<bits/stdc++.h>
using namespace std;
char str[100005];

int main() {
	int T, n;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d%s", &n, str + 1);
		char last = 0;
		for (int i = 1; str[i]; ++i)
		{
			char t = '1';
			if (str[i] + t == str[i - 1] + last)
				--t;
			printf("%c", last = t);
		}
		printf("\n");
	}
}

B(水题)

题目链接

题目:
给出一个\(d\),找出最小的自然数\(x\)满足,\(x\)至少含有4个因子(包括1和本身)且\(x\)所有因子之差大于等于\(d\)

解析:

  1. 很显然为了让\(x\)最小,那么一定保证4个因子中有1和本身两个因子
  2. 由于题目中要求所有因子都得大于等于\(d\),那么如果选择\(1+d,1+2d\)作为中间两个因子的情况下,如果二者不为素数,则二者的因数组合成的数(也就是\(x\))的因数不能保证差大于等于\(d\)
  3. 所以选定的数必须要保证是一对素数(其实通过打表也可以发现),这样便可以先预处理出所有素数,每次找出符合条件的素数因数即可
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/
const int maxn = 5e4;
int prime[maxn];
bool vis[maxn];
int cnt;

void euler(){
	for (int i = 2; i < maxn; ++i){
		if (!vis[i]) prime[cnt++] = i;
		for (int j = 0; j < cnt && prime[j] * i < maxn; ++j){
			vis[i * prime[j]] = true;
			if (i % prime[j] == 0)
				break;
		}
	}
}

int main(){
	euler();
	int T, n;
	scanf("%d", &T);
	while (T--){
		scanf("%d", &n);
		int* a = lower_bound(prime, prime + cnt, 1 + n);
		int* b = lower_bound(a + 1, prime + cnt, *a + n);
		printf("%lld\n", (LL)(*a) * (*b));
	}
}

C(思维)

题目链接
⭐⭐

题目:
给出\(2n\)个数,初始可以指定任意的数作为基数,然后将数组中和为基数的两个数删除,较大的数作为新的基数,问如此往复是否可以删除所有的数,并输出每轮的基数

解析:

  1. 通过推理,如果删除最大的数,那么基数一定是比这个最大的数和其他任意数的组合,在删除完后最大的数成为了基数,那么如果现存数中最大的数无法被删除,那么基数一定会被一个比现存最大的数更小的数所替代,那么这个现存数种最大的数永远无法被删除
  2. 那么就可以得知除了第一轮的基数(即最大的数和其他数的组合)是无法确定的以外,后续结果都已经固定(必须删除次大的数),检验过程通过检测现存的数中是否存在基数减去最大数的值即可,因此进\(O(n)\)的检验是否出现完全删除的情况即可

注意:

  1. 如果使用\(multiset\)存储当前剩余的数,一定要先从中删除最大的数,再去进行检测,避免如果二者值相等的情况下,会误认为结果仍可以成立
  2. 不使用\(set\)用桶进行存储查询也是同样的做法
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/
const int maxn = 2005;
const int M = 1e6 + 5;
int dat[maxn];
multiset<int, greater<int>> s;
vector<pair<int, int>> ans;
int n, T;

int main()
{
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &n);
		for (int i = 1; i <= 2 * n; ++i)
			scanf("%d", &dat[i]);
		sort(dat + 1, dat + 2 * n + 1);
		for (int i = 1; i < 2 * n; ++i)
		{
			ans.clear();
			s.clear();
			int last = dat[2 * n];
			ans.push_back(pair<int, int>(dat[2 * n], dat[i]));
			for (int j = 1; j < 2 * n; ++j) if (j != i) s.insert(dat[j]);
			while (!s.empty())
			{
				multiset<int, greater<int>>::iterator t;
				int tmp = *s.begin();
				s.erase(s.begin());
				if ((t = s.find(last - tmp)) != s.end())
				{
					ans.push_back(pair<int, int>(tmp, *t));
					last = tmp;
					s.erase(t);
				}
				else
					break;
			}
			if (ans.size() == n)
				break;
		}
		if (ans.size() == n)
		{
			printf("YES\n%d\n", ans[0].first + ans[0].second);
			for (auto& i : ans)
				printf("%d %d\n", i.first, i.second);
		}
		else
			printf("NO\n");
	}
}

D(思维)

题目链接
⭐⭐⭐

题目:
给出一组数,相邻的数值\(a,b\)可以减去\(\min(a,b)\),这个操作可以进行任意次,同时还可以在操作前交换一次相邻的数值,问是否可以将这组数清空?

解析:

  1. 对于这个删除操作,考虑将第一个数清空,则必须通过第二个数来实现,那么此时的第二个数的清空又必须要依靠后续的数,同理倒数第一个数、第二个数也是这样
  2. 那么如果选择\((i-1,i)\)进行交换,那么\(pre[i-2]\)\(suf[i+1]\)的部分是一定不会受到影响的,也就是说对于\(2\times(n-1)\)种交换的可能性种只需要检测\(pre[i-2],\underbrace{i,i+1}_{swap},suf[i+1]\)是否可以成立即可
  3. 对于已经无法成立的部分其\(pre\)\(suf\)\(-1\)表示,且对于\(pre\)如果遇到\(-1\)那后续必然都是\(-1\),\(suf\)如果遇到\(-1\)则前序必然都是\(-1\)

注意:在 0 与 n+1 的位置赋了 0 避免特判处理

#include<bits/stdc++.h>
#define rep(i,a,b) for(int  i=a;i<b;++i)
#define rep1(i,a,b) for(int  i=a;i<=b;++i)
#define rrep(i,a,b) for(int i=b-1;i>=a;--i)
#define rrep1(i,a,b) for(int i=b;i>=a;--i)
typedef long long LL;
using namespace std;
/*===========================================*/
const int maxn = 2e5 + 5;
int dat[maxn];
int pre[maxn], suf[maxn];

bool check(int i)
{
	return dat[i - 1] >= pre[i - 2] && dat[i] >= suf[i + 1] && dat[i - 1] - pre[i - 2] == dat[i] - suf[i + 1];
}

int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		int n;
		scanf("%d", &n);
		rep1(i, 1, n)
		{
			scanf("%d", &dat[i]);
			if (pre[i - 1] < 0 || dat[i] < pre[i - 1])
				pre[i] = -1;
			else
				pre[i] = dat[i] - pre[i - 1];
		}
		suf[n + 1] = 0;
		rrep1(i, 1, n)
			if (suf[i + 1] < 0 || dat[i] < suf[i + 1])
				suf[i] = -1;
			else
				suf[i] = dat[i] - suf[i + 1];
		bool ok = false;
		rep1(i, 2, n)
		{
			if (pre[i - 2] == -1 || suf[i + 1] == -1) continue;
			if (ok = check(i)) break;
			swap(dat[i - 1], dat[i]);
			if (ok = check(i)) break;
			swap(dat[i - 1], dat[i]);
		}
		printf("%s\n", ok ? "YES" : "NO");
	}
}

E(思维构造)

题目链接
⭐⭐⭐

题目:
给出一个长度\(n\),给出操作\((i,j),p[j]=i\Rightarrow swap(i,j)\),但要付出\((i-j)^2\)的代价,现需要求出对应的长度为\(n\)的排列可能拥有的最大代价、排列本身以及中间的操作过程

解析:

  1. 可以分析得出,交换可以使得当前值与值对应的位置进行交换,为了使交换的位置距离尽可能原,可以朴素的想到构造出形如\(2,3,4,\dots,n,1\)的序列,这样\((1,n)(2,n)\dots(n-1,n)\)不断进行交换,但这样却不是最优的
  2. 那么可以考虑构造形如\(n-1,3,4,\dots,\lfloor n/2\rfloor,1,n,\lfloor n/2\rfloor+1,\lfloor n/2\rfloor+2,\dots,n-2,2\)的序列,这样\((n-1,1)(n-2,1)\dots(\lfloor n/2\rfloor+1,1),(2,n)(3,n)\dots(\lfloor n/2\rfloor,n),(n,1)\)进行交换,这样不难得出所需要的次数是\((n-1)^2+2*\left((n-2)^2+\dots+(n-\lfloor n/2\rfloor)^2\right)\),再判断是否\(\lfloor n/2\rfloor\)为奇数加上\((n-1-\lfloor n/2\rfloor)^2\)
  3. 其实上述的构造方式相当于第一种朴素构造的变形,从两边开始找尽可能长的交换路径(即两端非端点的位置开始向内部进行交换)

注意:

  1. 要开\(long\ long\)
  2. \(n=2,n=3\)的情况进行特判
#include<bits/stdc++.h>
#define rep(i,a,b) for(int  i=a;i<b;++i)
#define rep1(i,a,b) for(int  i=a;i<=b;++i)
#define rrep(i,a,b) for(int i=b-1;i>=a;--i)
#define rrep1(i,a,b) for(int i=b;i>=a;--i)
typedef long long LL;
using namespace std;
/*===========================================*/

const int maxn = 1e5 + 5;
LL sum[maxn];

int main() {
	rep(i, 1, maxn)
		sum[i] = sum[i - 1] + 1LL * i * i;
	int T;
	scanf("%d", &T);
	while (T--) {
		long long n;
		scanf("%lld", &n);
		long long t = n / 2;
		if (n == 2)
			printf("1\n2 1\n1\n2 1\n");
		else if (n == 3)
			printf("5\n2 3 1\n2\n2 1\n1 3\n");
		else
		{
			printf("%lld\n", (n - 1) * (n - 1) + 2 * (sum[n - 2] - sum[n - 1 - t]) + (n & 1 ? (n - 1 - t) * (n - 1 - t) : 0));
			printf("%lld", n - 1);
			rep(i, 2, t)
				printf(" %d", i + 1);
			printf(" 1 %lld", n);
			rep(i, t + 2, n)
				printf(" %d", i - 1);
			printf(" 2\n");
			printf("%lld\n", n - 1);
			rep1(i, 2, t)
				printf("%d %lld\n", i, n);
			rrep(i, t + 1, n)
				printf("%d 1\n", i);
			printf("%lld 1\n", n);
		}
	}
}
posted @ 2021-01-24 18:24  DreamW1ngs  阅读(129)  评论(0编辑  收藏  举报