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\)
解析:
- 很显然为了让\(x\)最小,那么一定保证4个因子中有1和本身两个因子
- 由于题目中要求所有因子都得大于等于\(d\),那么如果选择\(1+d,1+2d\)作为中间两个因子的情况下,如果二者不为素数,则二者的因数组合成的数(也就是\(x\))的因数不能保证差大于等于\(d\)
- 所以选定的数必须要保证是一对素数(其实通过打表也可以发现),这样便可以先预处理出所有素数,每次找出符合条件的素数因数即可
#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\)个数,初始可以指定任意的数作为基数,然后将数组中和为基数的两个数删除,较大的数作为新的基数,问如此往复是否可以删除所有的数,并输出每轮的基数
解析:
- 通过推理,如果删除最大的数,那么基数一定是比这个最大的数和其他任意数的组合,在删除完后最大的数成为了基数,那么如果现存数中最大的数无法被删除,那么基数一定会被一个比现存最大的数更小的数所替代,那么这个现存数种最大的数永远无法被删除
- 那么就可以得知除了第一轮的基数(即最大的数和其他数的组合)是无法确定的以外,后续结果都已经固定(必须删除次大的数),检验过程通过检测现存的数中是否存在基数减去最大数的值即可,因此进\(O(n)\)的检验是否出现完全删除的情况即可
注意:
- 如果使用\(multiset\)存储当前剩余的数,一定要先从中删除最大的数,再去进行检测,避免如果二者值相等的情况下,会误认为结果仍可以成立
- 不使用\(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)\),这个操作可以进行任意次,同时还可以在操作前交换一次相邻的数值,问是否可以将这组数清空?
解析:
- 对于这个删除操作,考虑将第一个数清空,则必须通过第二个数来实现,那么此时的第二个数的清空又必须要依靠后续的数,同理倒数第一个数、第二个数也是这样
- 那么如果选择\((i-1,i)\)进行交换,那么\(pre[i-2]\)与\(suf[i+1]\)的部分是一定不会受到影响的,也就是说对于\(2\times(n-1)\)种交换的可能性种只需要检测\(pre[i-2],\underbrace{i,i+1}_{swap},suf[i+1]\)是否可以成立即可
- 对于已经无法成立的部分其\(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\)的排列可能拥有的最大代价、排列本身以及中间的操作过程
解析:
- 可以分析得出,交换可以使得当前值与值对应的位置进行交换,为了使交换的位置距离尽可能原,可以朴素的想到构造出形如\(2,3,4,\dots,n,1\)的序列,这样\((1,n)(2,n)\dots(n-1,n)\)不断进行交换,但这样却不是最优的
- 那么可以考虑构造形如\(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\)
- 其实上述的构造方式相当于第一种朴素构造的变形,从两边开始找尽可能长的交换路径(即两端非端点的位置开始向内部进行交换)
注意:
- 要开\(long\ long\)
- 对\(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);
}
}
}
努力变成更好的自己吧!