A、Make it Beautiful
一种构造方法:按照从大到小的顺序构造,可以发现前缀和永远大于当前值。但是需要特判存在两个最大值的情况。只需要将最小值与第二位交换位置即可。最后暴力判一遍防止数字全部相等。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
bool cmp(int a, int b){
return a > b;
}
int a[100010];
int b[100010];
int main(){
int T;
cin >> T;
while(T--){
int n;
cin >> n;
for(auto i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1, cmp);
if(a[1] == a[2]){
b[1] = a[1];
b[2] = a[n];
for(int i = 3; i <= n; i++)
b[i] = a[i-1];
}
else for(int i = 1; i <= n; i++) b[i] = a[i];
int sum = 0;
bool flag = 1;
for(int i = 1; i <= n; i++){
if(b[i] == sum){
flag = 0;
break;
}
sum += b[i];
}
if(flag){
puts("YES");
for(int i = 1; i <= n; i++)
printf("%d ", b[i]);
cout << endl;
}
else puts("NO");
}
return 0;
}
B、Matrix of Differences
纯智商题。可以发现最大值一定是 \(n^2 - 1\) 种,即 \([1, n^2 - 1]\) 中的所有整数都会出现。于是考虑最大与最小挨着构造(只有这样才能出现差值为 \(n^2 - 1 、n^2 - 2 ......\) 的情况)。
那么考虑斜对角线交替放数,一条线从小到大,一条线从大到小,依次放入。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 100010
int a[51][51];
int main(){
int T; cin >> T;
while(T--){
int n; cin >> n;
memset(a, 0, sizeof(a));
a[1][1] = 1;
int del = 1;
bitset <10010> vis(0);
int now = 1; int now2 = n * n;
for(int i = 1; i <= n; i++){
int x = i, y = 1;
if(i % 2 == 0){
while(x >= 1 && y <= n){
a[x][y] = now;
now++;
x--, y++;
}
}
else{
while(x >= 1 && y <= n){
a[x][y] = now2;
now2--;
x--, y++;
}
}
}
for(int i = 2; i <= n; i++){
int x = n, y = i;
if(i % 2 != (n % 2)){
while(x >= 1 && y <= n){
a[x][y] = now;
now++;
x--, y++;
}
}
else{
while(x >= 1 && y <= n){
a[x][y] = now2;
now2--;
x--, y++;
}
}
}
for(int i = 1; i <= n; i++, puts("")){
for(int j = 1; j <= n; j++){
printf("%d ", a[i][j]);
}
}
}
return 0;
}
C、Yet Another Tournament
(摘编自自己的洛谷题解)
题目大意
首先容易推出假如不算我们自己,那么剩下的敌人的得分就是数列 \(a_i = i\)。现在我们要做的就是花一定数量的时间来训练对应一部分敌人,打败他们同时自己获得一分(当然被打败的那个敌人就少获得一分)。
主要思路
容易想到我们肯定是想要自己的分尽量高,那么显然就是贪心,从小到大选择花费时间最小的敌人,一直到总和超过 \(m\) 为止。但存在一个问题,就是未被我们选中的敌人会增加 \(1\) 分。下面就此进行讨论。
假设我们按照贪心的方法选择了 \(k\) 个敌人进行训练,那么容易知道 \(k\) 就是我们自身的最大得分。假设有一敌人的初始得分大于 \(k\), 那我们完全就可以忽略他。因为显而易见的是无论我们如何“努力”,我们都不可能战胜他。对于初始得分小于 \(k\) 的敌人,显然排名永远会落后我们。所以,我们只需要考虑剩下一个唯一情况,就是初始得分等于 \(k\) 的人。假设我们在保持总分不变的情况下没有选择战胜他,那么我们就会损失一个排名。所以尝试将该人与最大值进行交换,尽可能选中他。
正确性
下面简略说一下上述方法的正确性(最优性)。假设我们的得分为 \(k\), 那么我们能战胜的敌人就是 \(k\) 个(不算临界情况)。如果我们能将临界者战胜,就是 \(k+1\) 个。如果为了加入当前临界者而导致 \(k\) 减少一,显然也不优。所以最大化 \(k\) 并尽量拉入临界者是最优做法。
代码时间
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define N 500010
int n, m;
struct node{
int w, id;
} t[N];
int a[N];
int b[N];
bool cmp1(node a, node b){
return a.w < b.w;
}
int main(){
int T; cin >> T;
while(T--){
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> t[i].w;
t[i].id = i;
a[i] = t[i].w;
}
sort(t + 1, t + n + 1, [](node a, node b){
return a.w < b.w;
});
bitset <N> vis(0);
int sum = 0, maxn = n, ans = 0;
for(int i = 1; i <= n; i++){
if(sum + t[i].w > m){
maxn = i-1;
break;
}
sum += t[i].w;
vis[t[i].id] = 1;
ans++;
}
if(!vis[ans+1]){
if(sum - t[maxn].w + a[ans+1] <= m){
vis[ans+1] = 1;
vis[t[maxn].id] = 0;
}
}
for(int i = 1; i <= n; i++)
b[i] = i - 1 + (!vis[i]);
b[n+1] = ans;
sort(b + 1, b + n + 2, [](int a, int b) -> bool {return a > b; }); // 排序计算排名
int num = 0;
for(int i = 1; i <= n + 1; i++){
num++;
if(b[i] == ans){
break;
}
}
cout << num << endl;
}
return 0;
}