牛客周赛 Round 82 D/E/F
D-小苯的排列技术
原题链接:https://ac.nowcoder.com/acm/contest/102303/D
思路:
- 先判断序列是否有可行方案,即判断序列为非递增序列且最后一位一定为1。
- 在数字 \(P\) 在序列中出现的第一个位置即为其应该放置的位置,并且在下一个数字在序列中出现的位置与当前 \(P\) 所在位置间的个数,即为所要填的值要大于 \(P\) 的数字的个数。
记 \(cnt\) 为序列前缀中已经出现的数字的个数、\(sum\) 为已经填过的数字的个数、\(x\) 为 \(P\) 出现的次数减一,则此时大于 \(P\) 且可选数字的个数 \(t\) 为 \(n-p-cnt-sum\)。
如此时t的值小于0,那么不合法答案为0,否则答案要乘上 \(C(t,x)*A(x,x)\)。
时间复杂度:\(O(n)\)
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
int n;
vector<int> pre;
bool check(){
int last=pre[0];
for(int i=1;i<n;i++){
if(last<pre[i]) return false;
last=pre[i];
}
return last==1;
}
void solve(){
cin>>n;
pre=vector<int>(n);
for(int i=0;i<n;i++)cin>>pre[i];
if(!check()){
cout<<0<<endl;
return;
}
ll ans=1;
int cnt=0,sum=0;
for(int i=0;i<n;i++){
int p=pre[i],j=i;
while(j<n&&pre[j+1]==p)j++;
int x=j-i;
int t=n-p-cnt-sum;
if(t<0){
ans=0;
break;
}
for(int i=t-x+1;i<=t;i++)ans=ans*i%mod;
cnt++,sum+=x;
i=j;
}
cout<<ans<<endl;
}
int main(){
cin.tie(0)->sync_with_stdio(false);
cout.tie(0);
int t;
cin>>t;
while(t--)solve();
}
E-和+和
题目链接:https://ac.nowcoder.com/acm/contest/102303/E
思路:
\(a\) 序列的最大索引小于 \(b\) 序列的最小索引,可以固定 \(a\) 序列最大索引的位置,先分别处理 \(a\) 前缀 \(b\) 后缀的的 \(m\) 项最小和,这个操作可以通过维护大根堆来进行计算,然后遍历所有 \(a\) 的最大索引可能的位置找到答案。
时间复杂度:\(O(nlog(m))\)
代码
// 前m小和: 维护前m小的小根堆即可 每次动态对值进行维护
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
int main(){
cin.tie(0)->sync_with_stdio(false);
cout.tie(0);
cin>>n>>m;
vector<ll> a(n+1),b(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
vector<ll> prea(n+1,0),sufb(n+1,0);
priority_queue<ll,vector<ll>,less<ll>> ha,hb;
ll suma=0,sumb=0;
for(int i=1;i<=n;i++){
ha.push(a[i]);
suma+=a[i];
if(ha.size()>m){
suma-=ha.top();
ha.pop();
}
prea[i]=suma;
}
for(int i=n;i;i--){
hb.push(b[i]);
sumb+=b[i];
if(hb.size()>m){
sumb-=hb.top();
hb.pop();
}
sufb[i]=sumb;
}
ll ans=1e18;
for(int i=m;n-i>=m;i++){
ans=min(ans,prea[i]+sufb[i+1]);
}
cout<<ans;
}
F-怎么写线性SPJ
原题链接:https://ac.nowcoder.com/acm/contest/102303/F
思路:
思维题,因为整个序列一定有单个元素,不妨为\(1\),那么我们将\(1\)放到构造的序列的中间位置,然后分别考虑被分开的两部分,选取单独一部分也一定有单个元素,并且可相同,因为若同时选取,那么一定会把1包含在内,一定符合题意,那么答案是类似:\(... 4 3 4 2 4 3 4 1 4 3 4 2 4 3 4 ...\) 这样的序列,其中 \(1\) 最多一个,\(2\) 最多两个,\(3\) 最多4个... \(n\) 最多 \(2^(n-1)\) 个。那么最少种类数就可以找到,然后序列可以编写递归程序去构造。
时间复杂度 O(n)
代码:
#include<bits/stdc++.h>
using namespace std;
void dfs(vector<int> &a,int l,int r,int c){
if(l>r) return;
int mid=l+r>>1;
a[mid]=c;
dfs(a,l,mid-1,c+1),dfs(a,mid+1,r,c+1);
}
int main(){
cin.tie(0)->sync_with_stdio(false);
cout.tie(0);
int n;
cin>>n;
int t=0,p=1,s=0;
while(s<=n){
s+=p;
t++;
p<<=1;
}
cout<<t<<endl;
vector<int> ans(n+1,0);
dfs(ans,1,n,1);
for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
}