牛客周赛 Round136 题解
A.小红的数组重排
题意:
给出三个数字的数组,让他们重新排列一下,使新的数组和原来的数组的每个位置上的数字都不相同
思路:
直接错位一个就可以
代码
点击查看代码
int main(){
int a[3];
for(int i=0;i<3;i++)cin>>a[i];
for(int i=1;i<3;i++)cout<<a[i]<<' ';
cout<<a[0];
}
B.小红的回文串构造
题意
t组数据每组给出一个n,问能否构造一个长度为n的所有字符都互不相同的只有小写字母的回文串,可以就输出这个回文串,不可以输出No
思路:
显然只有长度是1的时候才有可能构造出这样的回文串
代码:
点击查看代码
void solve(){
int n;
cin>>n;
if(n>1){
cout<<"No"<<endl;
}else{
cout<<'a';
cout<<endl;
}
}
C.小红的排列
题意:
给出一个长度为n的字符串,里面只有oj?,让我们根据这个字符串提供的信息:o代表当前的位置只能排偶数,j代表当前的位置只能排奇数,?代表偶数奇数都可以。构造出一个长度为n的排列,问一共有多少个排列方式,答案对998244353取模
长度为n的排列由1,2,3....n的数字按任意顺序组成,每个数字只出现一次。
思路:
因为长度n已知,我们实际上知道这个排列里面奇数和偶数有多少个,我们先对字符串里面的奇数偶数和问号进行计数,如果奇数或者偶数需要排进去的数量大于这个排列中总的这个数字的数量那么一定是不可能完成的就是0。运用高中的排列组合知识,我们由两种思路来完成,首先共同的,只要字符串里面有问号,而且oj的数量没有超过总的数量,那么说明?的数量里面一部分是缺少的奇数一部分是偶数。思路一是先在?里面把缺少的奇数选择位置,剩下的就是奇数个数的全排列,偶数的全排列,三者相乘。思路二是从左往右遇到什么就从什么里面选一个
代码:
因为错题本里面写过两个的代码,而且思路二的代码过不了这道题。所以这里只记录思路一的代码
错题本链接第二题
点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll mod=998244353;
struct Z {
ll x;
Z(ll x = 0) : x(x % mod) { if (x < 0) x += mod; }
Z operator*(const Z& other) const { return Z(x * other.x); }
// 如果需要加法也可以加上,虽然这段代码主要用乘法
Z operator+(const Z& other) const { return Z(x + other.x); }
};
struct Comb {
vector<ll> fact, inv_fact;
Comb(int n = 0) { init(n); }
void init(int n) {
fact.resize(n + 1);
inv_fact.resize(n + 1);
fact[0] = 1;
for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i % mod;
// 快速幂求逆元
inv_fact[n] = pow_mod(fact[n], mod - 2);
for (int i = n - 1; i >= 0; i--) inv_fact[i] = inv_fact[i + 1] * (i + 1) % mod;
}
ll pow_mod(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
// 计算 C(n, m)
ll C(int n, int m) {
if (m < 0 || m > n) return 0;
return fact[n] * inv_fact[m] % mod * inv_fact[n - m] % mod;
}
// 获取阶乘
ll fac(int n) {
return fact[n];
}
} C;
int main(){
int n;
string s;
cin>>n>>s;
ll cnt_j,cnt_o,cnt_a;
cnt_o=n/2;
cnt_j=n-cnt_o;
cnt_a=n;
ll ans=1;
C.init(n);
for(int i=0;i<n;i++){
if(s[i]=='j'){
ans=ans*cnt_j%mod;
cnt_j--;
cnt_a--;
}
else if(s[i]=='o'){
ans=ans*cnt_o%mod;
cnt_o--;
cnt_a--;
}
}
if(cnt_o<0||cnt_j<0||cnt_o+cnt_j!=cnt_a){
cout<<0<<endl;
return 0;
}
ans = (ans * C.C(cnt_a, cnt_o) % mod) * C.fac(cnt_j) % mod*C.fac(cnt_o)%mod;
cout<<ans<<endl;
}
D.小红的中位数
题意:
有一个长度为n的数组,小红每次可以选择数组中的一个数字删除(无法将数组删除至空),最少需要多少次操作才可以使这个数组的中位数发生变化,如果无论如何人都无法改变输出-1
思路:
我们可以将数组分为三类,l,x,r分别代表小于最开始中位数x的部分,等于中位数的部分,大于x的部分。从最终的结果来看中位数只有变小或者变大两种情况,如果中位数变小那么我们尽量不要删除l的部分,那么最终的长度最大是,2l,刚好使得中位数成为小于x的最后一个数字。如果中位数变大,那么尽量不要删除r的部分,最终的长度是最长是2r-1,比较两个长度哪个更长,输出差距即可。特殊得,如果全部的数字都一样输出-1.
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll mod=998244353;
int main(){
int n;
cin>>n;
vector<int>a(n);
for(int i=0;i<n;i++)cin>>a[i];
sort(a.begin(),a.end());
int mid=a[(n-1)/2];
int l=lower_bound(a.begin(),a.end(),mid)-a.begin();
int r=a.end()-upper_bound(a.begin(),a.end(),mid);
if(l==0&&r==0){
cout<<-1<<endl;
return 0;
}
int ans=n+1;
if(l>0)
ans=min(ans,n-2*l);
if(r>0)
ans=min(ans,n-(2*r-1));
cout<<ans<<endl;
}
E.小红的树上博弈
题意:
有一棵树,小红和小紫进行博弈,一开始所有的节点都是红色的,规定小红每次只能到相邻的红色的节点,小紫每次只能将小红相邻的一个节点变成紫色,小红从根节点1开始先手,问小红能否走到叶子节点
叶子节点是这个节点没有子节点且不是根节点,或者只有一个节点的时候。
思路及代码:
所有的想法都写在错题本里面,不想再写第二遍一摸一样的了
错题本链接第五题
F.小红的点构造
题意:
能否构造n个坐标使得他们里面刚好有k个点位在曼哈顿的距离下相邻,无解输出No,否则输出Yes,然后n行每行一个坐标,使满足条件
两个点(x1,y1),(x2,y2)的曼哈顿距离是|x1-x2|+|y1-y2|,相邻指的是曼哈顿距离为一
思路:
我们首先构造n个点按照正方形的形式排列,计算出每加入一个点总的相邻对的个数。我们需要判断已经将n个左边最大化相邻对的个数的情况下能否满足k,然后将一次输出,需要注意的是最后一个点的输出可能会使总的相邻对个数+2,所以需要特殊判断。如果已经输出了足够的相邻对,还是不够n个坐标需要我们在不影响相邻对的情况下输出他们的坐标
代码:
点击查看代码
using ll=long long;
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
void solve() {
int n, k;
cin >> n >> k;
vector<PII> P;///存进去的曼哈顿距离是1的点位
vector<ll> E;///这个的back表示现在有多少个距离1
P.pb({0, 0});///最开始从这个点开始存
E.pb(0);
int d = 1;///正方形的边长
while (P.size() < n) {
///先找上面的点(不包含角上)
for (int x = 0; x < d && P.size() < n; ++x) {
P.pb({x, d});
E.pb(E.back() + (x == 0 ? 1 : 2));///除了原点正上方的那个点,其他的点都会和两个其他的点形成曼哈顿距离为1的距离,而相邻对就会+2
}
///找右边的点(不包含顶点)
for (int y = 0; y < d && P.size() < n; ++y) {
P.pb({d, y});
E.pb(E.back() + (y == 0 ? 1 : 2));
}
///如果当前边长两边的点都进去了还是不够就加上顶点
if (P.size() < n) {
P.pb({d, d});
E.pb(E.back() + 2);///顶点也是会形成距离2
}
d++;
}
if (E.back() < k) {///如果所有点总的距离也就是相邻对都不满足要求
cout << "No" << endl;
return;
}
cout << "Yes" << endl;
int pos = 0;
for (int i = 0; i < n; ++i) {
if (E[i] <= k) {
pos = i + 1;///pos找到第一个输出了的相邻对大于要求的位置
}
}
for (int i = 0; i < pos; ++i) {
cout << P[i].fi << " " << P[i].se << endl;
}
int r = k - E[pos - 1], rem = n - pos;
///r代表还剩下多少相邻的没有输出
///rem代表n里面还有多少对
if (rem) {
if (r == 1) {///由于E每次加上去都是1或者2,所以r只有0或1的情况
int mx = -1, yy = -1;
for (int i = 0; i < pos; ++i) {///找到现在x坐标最大的一个点,在他的右边加上一个点,刚好使相邻对加1
if (P[i].fi > mx) {
mx = P[i].fi;
yy = P[i].se;
}
}
cout << mx + 1 << " " << yy << endl;
rem--;
}
int x = -1e8, y = -1e8;
while (rem > 0) {///输出剩下的点位,让他们不相邻
cout << x << " " << y << endl;
x += 2;
rem--;
}
}
}
浙公网安备 33010602011771号