牛客 周赛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;//标记是否到nfor (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次