牛客 周赛82 20250227
牛客 周赛82 20250227
https://ac.nowcoder.com/acm/contest/102303
A:
题目大意:给定字符串 \(s\) ,判断首尾是否相同
#include<bits/stdc++.h>
#define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Trd int T;cin>>T;while (T--)solve();
#define LLinf 9e18;
#define Iinf 2e9
#define LL long long
#define Lc p<<1
#define Rc p<<1|1
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
int main()
{
string s;
cin>>s;
if(s[0]==s[s.size()-1]) cout<<"YES";
else cout<<"NO";
return 0;
}
简单签到
B:
题目大意:

#include<bits/stdc++.h>
#define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Trd int T;cin>>T;while (T--)solve();
#define LLinf 9e18;
#define Iinf 2e9
#define LL long long
#define Lc p<<1
#define Rc p<<1|1
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
int n;
int a[1010];
int main()
{
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
for (int i=2;i<=n;i++){
if (a[i]==a[i-1]){
cout<<"NO";
return 0;
}
}
cout<<"YES";
return 0;
}
将窗口人数从小到大排序,当且仅当 \(a_{i+1}>a_i\) 时才能保证有充足的时间进行拍照操作
C:
题目大意:与B题共享背景,数据范围增大且需要输出排队路径
#include<bits/stdc++.h>
#define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Trd int T;cin>>T;while (T--)solve();
#define LLinf 9e18;
#define Iinf 2e9
#define LL long long
#define Lc p<<1
#define Rc p<<1|1
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
struct node{
int t,pos;
};
int n;
vector<node> a;
int main()
{
cin>>n;
for (int i=1;i<=n;i++){
int x;
cin>>x;
a.push_back({x,i});
}
sort(a.begin(),a.end(),[](node x,node y){return x.t<y.t;});
for (int i=1;i<n;i++){
if (a[i].t==a[i-1].t){
cout<<"NO";
return 0;
}
}
cout<<"YES"<<endl;
for (auto iter:a)
cout<<iter.pos<<' ';
return 0;
}
将每个窗口的编号与人数绑定,按照人数排序后,与B题类似的进行操作,最后输出窗口编号即可
D:
题目大意:有一个 \([1,n]\) 的排列,现在给出确定前缀最小值数组 \(a\) ,计算满足这个前缀最小值数组的排列个数
#include<bits/stdc++.h>
#define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Trd int T;cin>>T;while (T--)solve();
#define LLinf 9e18;
#define Iinf 2e9
#define LL long long
#define Lc p<<1
#define Rc p<<1|1
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
const int mod=998244353;
void solve(void){
int n;
cin>>n;
vector<int> a(n+10);
for (int i=1;i<=n;i++) cin>>a[i];
a[0]=1e9;
LL ans=1;
int now=n-a[1];
for (int i=2;i<=n;i++){
if (a[i]>a[i-1]){
cout<<0<<endl;
return;
}
if (a[i]==a[i-1]){
ans=ans*now%mod;
now--;
}
if (a[i]<a[i-1]){
now+=a[i-1]-a[i]-1;
}
ans%=mod;
}
cout<<ans<<endl;
}
int main()
{
Trd;
return 0;
}
前缀最小值数组的性质存在: \(a_{i}<a_{i-1}\implies\) 可以确定填在 \(i\) 位置上的数为 \(a_i\)
所以根据 \(a_i\) ,考虑每个位置上的数能选取的数字个数
初始化时,now=n-a[1] ,即从第二位开始考虑,能选的数字个数
-
a[i]>a[i-1]显然不能构成一个正确的排列,输出 \(0\) -
a[i]==a[i-1]当前位置上没有确定数字,那么从可选的数字中取一个填入,对答案的贡献根据乘法原理,ans=ans*now -
a[i]<a[i-1]当前位置可以确定一个数,更新可选的数now+=a[i-1]-a[i]-1
巧妙的是,过程中如果某一位无数可选那么对应的 \(now=0\) ,所以 \(ans\times0=0\) ,最后输出答案同样为 \(0\) ,符合题意
E:
题目大意:给定 \(n\) 个元素的两个数组 \(a,b\) ,从小到大选取 \(2\times m\le n\) 个下标,计算对于所有的下标组合得出的表达式的最小值
#include<bits/stdc++.h>
#define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Trd int T;cin>>T;while (T--)solve();
#define LLinf 9e18;
#define Iinf 2e9
#define LL long long
#define Lc p<<1
#define Rc p<<1|1
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
int main()
{
int n,m;
cin>>n>>m;
priority_queue<int> qa;
LL suma=0;
vector<LL> pa(n+10);
for (int i=1;i<=n;i++){
int a;
cin>>a;
if(i<=m){
qa.push(a);
suma+=1ll*a;
}else{
if (a<qa.top()){
suma-=qa.top();
qa.pop();
qa.push(a);
suma+=a;
}
}
if (i>=m)
pa[i]=suma;
}
vector<int> b(n+10);
for (int i=1;i<=n;i++) cin>>b[i];
priority_queue<int> qb;
LL sumb=0;
vector<LL> pb(n+10);
for (int i=n;i>=1;i--){
if (i>=n-m+1){
qb.push(b[i]);
sumb+=1ll*b[i];
}else{
if (b[i]<qb.top()){
sumb-=qb.top();
qb.pop();
qb.push(b[i]);
sumb+=b[i];
}
}
if (i<=n-m+1)
pb[i]=sumb;
}
LL ans=LLinf;
for (int i=m;i<=n-m;i++)
ans=min(ans,pa[i]+pb[i+1]);
cout<<ans;
return 0;
}
预处理+堆优化
因为 $2\times m\le n\iff m\le \frac{n}{2} $,所以需要选取的 \(a_{i_1}+a_{i_2}+\cdots+a_{i_m}+b_{i_{m+1}}+b_{i_{m+2}}+\cdots+b_{i_{2*m}}\) 可以看作在 \(a\) 中选 \(m\) 个元素,在 \(b\) 中从 \(m+1\) 的下标开始再选 \(m\) 个元素
可以这样对称地来看
于是可以预处理出 \(a\) 数组前 \(i\) 个元素选出 \(m\) 个元素的最小和,以及 \(b\) 数组后 \(i\) 个元素选出 \(m\) 个元素的最小和
采用优先队列大根堆优化,注意边界情况
if(i<=m){
qa.push(a);
suma+=1ll*a;
}//在a中选,只有i>m后才有意义
if (i>=m)
pa[i]=suma;
//边界情况,i=m时也需要记录
最后的答案
for (int i=m;i<=n-m;i++)
ans=min(ans,pa[i]+pb[i+1]);
枚举 \(i_m\) 分界线,范围在 \([m,n-m]\) 内,记录所需最小值即可
F:
题目大意:给定 \(n\) ,存在数组满足数组内每一个元素都是 \([1,n]\) 之间的整数,并且数组的每一个非空连续子区间都至少存在一个 "唯一元素" ,现构造一个由 \(n\) 个元素组成的、"种类数" 最少的这类数组
*唯一元素:定义一个非空数组的 "唯一元素" 为该数组中只出现一次的元素。一个数组可能有多个 "唯一元素" 、也可能没有 "唯一元素"
*种类数:定义一个数组的 "种类数" 为该数组中不同的元素个数
#include<bits/stdc++.h>
#define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Trd int T;cin>>T;while (T--)solve();
#define LLinf 9e18;
#define Iinf 2e9
#define LL long long
#define Lc p<<1
#define Rc p<<1|1
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
using namespace std;
int main()
{
int n;
cin>>n;
vector<int> a;
a.push_back(1);
int cnt=1;
while (a.size()<n){
vector<int> b;
for (auto it:a) b.push_back(it);
bool f=0;//标记是否到n
for (int i=0;i<b.size()-1;i++){
if (a.size()==n){
f=1;
break;
}
a.push_back(b[i]);//类似倍增的构造
}
if (f) break;
a.push_back(++cnt);//修改最后的字符
}
cout<<cnt<<endl;
for (auto it:a) cout<<it<<' ';
return 0;
}
思维构造
构造数组,当其中一个连续数组跨度为偶数时,其中的元素一定存在惟一个
将这个连续数组抽象为 \(A+B\) ,那么 \(A,B\) 一定不相同,贪心地想需要种类数最少情况,所以只让 \(B\) 其中一个元素与 \(A\) 不同即可
例如
1
1 2
1 2 1 3
1 2 1 3 1 2 1 4
1 2 1 3 1 2 1 4 1 2 1 3 1 2 1 5
\(B\) 除开最后一个字符与 \(A\) 不同,其他元素都可以由 \(A\) 复制过来,这样构造出的数组种类数一定最少,并且可以由前面的状态递推出
类似于倍增地计算,当 \(n\) 为 \(2\) 的整数幂时,\(B\) 最后的字符需要修正为一个新元素
数据量是10^3,手动打表只需要10次

浙公网安备 33010602011771号