省流:40min 切 A~E 然后 F 一个特殊情况 n=1 没调出来 AC27 WA3 哭死。
C - Between P and Q
一开始想要用 \(n\) 进制数去相减,但是要保证是一个排列就不能这样做了。
但是 \(n\) 很小,可以直接全排列,复杂度为 \(\mathcal O(n\times n!)\),可以过。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 20;
int n,p[N],q[N],a[N],c[N],buc[11],ans;
void dfs(int x){
if(x>n){
for(int i=1;i<=n;++i)buc[i]=0;
for(int i=1;i<=n;++i)++buc[a[i]];
bool ok=1;
for(int i=1;i<=n;++i){
if(buc[i]!=1)ok=0;
}
for(int i=1;i<=n;++i){
if(a[i]<p[i]){
ok=0;
break;
}
if(a[i]>p[i])break;
if(i==n&&a[i]==p[i])ok=0;
}
for(int i=1;i<=n;++i){
if(a[i]>q[i]){
ok=0;
break;
}
if(a[i]<q[i])break;
if(i==n&&a[i]==q[i])ok=0;
}
ans+=ok;
}
for(int i=1;i<=n;++i){
if(!c[i]){
c[i]=1;
a[x]=i;
dfs(x+1);
a[x]=0;
c[i]=0;
}
}
}
signed main(){
cin>>n;
int n1=0,n2=0;
for(int i=1;i<=n;++i){
cin>>p[i];
}
for(int i=1;i<=n;++i){
cin>>q[i];
}
dfs(1);
cout<<ans;
return 0;
}
D - Pre-Palindrome
注意到 \(n\) 只有 \(10^4\),因此可以 \(n^2\) 暴力。
暴力的过程就是中心扩展,每次枚举中点然后向左和右扩展,直到不成对的超过 \(1\) 对。注意存在奇回文串和偶回文串,因此枚举时还要考虑偶数,此时枚举两个即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4+1;
string s,ss;
int n,dp[N][N];
signed main(){
cin>>s;
n=s.size();
s=" "+s;
int ans=0;
for(int i=1;i<=n;++i){
int r=0,num=0;
while(i+r<=n&&i-r>=1&&num<=1){
if(s[i+r]!=s[i-r])++num;
if(num<=1)++r,++ans;
}
}
for(int i=1;i<n;++i){
int r=0,num=0;
while(i-r>=1&&i+1+r<=n&&num<=1){
if(s[i-r]!=s[i+1+r])++num;
if(num<=1)++r,++ans;
}
}
cout<<ans;
return 0;
}
E - Sum of Average
套路题,5 min 切。
直接枚举区间长度,对同一种长度的区间进行求和。我们设区间长度是 \(k\),前缀和是 \(pre_i\),那么我们枚举区间 \([i,i-k+1]\),即为 \(\frac{pre_i-pre_{i-k}}{k}\),求和之后分子为 \(pre_k+pre_{k+1}+\dots +pre_n-(pre_1+pre_2+\dots +pre_{n-k})\),于是考虑前前缀和就可以了。时间复杂度为 \(\mathcal O(n)\)。
然后记得取模,我就 tm 取模不到位吃了 2 发罚时。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5+10;
const int mod = 998244353;
int n,a[N],pre[N],ppre[N];
int qpow(int a,int p,int m){
int res=1;
while(p){
if(p&1)res=res*a%m;
a=a*a%m;
p>>=1;
}
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
pre[i]=(pre[i-1]+a[i])%mod;
ppre[i]=(ppre[i-1]+pre[i])%mod;
}
int ans=0;
for(int i=1;i<=n;++i){
// ppre[n]-ppre[i-1]-ppre[n-i]?
ans+=((ppre[n]-ppre[i-1]-ppre[n-i])%mod+mod)%mod*qpow(i,mod-2,mod)%mod;
ans%=mod;
}
cout<<ans;
return 0;
}
F - Chmax
写的是正解但是 \(n=1\) 寄掉了,呜呜呜。
首先 \(p_1\) 是必须要操作的,不妨设给 \(x\)。然后我们考虑前缀最大值,一个数是前缀最大值的话,无论如何都会增加 \(1\) 的值,这很好理解,于是我们将这个排列用前缀最大值分段为:
其中 \(t_i\) 表示 \([1,t_i]\) 的最大值是 \(p_{t_i}\),其中 \(t_1=1,t_m=n\)。
我们选择这样进行操作。遇到 \(p_{t_i}\) 我们就放到 \(x\) 那里,遇到 \(p_{t_i}\) 和 \(p_{t_{i+1}}\) 之间的可以选择 \(x\) 那里,只不过此时不增加 \(c\) 值且不改变 \(x\),也可以替换 \(y\),那么就可以作为一个待定项。然后有一个必然,如果某一时刻我不把 \(p_{t_i}\) 替换 \(x\) 而是选择 \(y\),这样一定不优,证明如下:
此时 \(x\) 为 \(p_{t_{i-1}}\),观察 \(p_{t_i}-x\) 和 \(p_{t_i}-y\),由于 \(y\) 一定不是 \(p_{t_j}\),因为我们假定当前是第一次出现这样的操作,所以前面的 \(p_{t_j}\) 都给了 \(x\),所以 \(y\) 要么在 \(p_{t_{i-1}}\) 与 \(p_{t_i}\) 之间,要么在 \(p_{t_{i-1}}\) 之前,而这两种情况都满足 \(y<x\),于是有 \(p_{t_i}-x<p_{t_i}-y\),也就是 \(p_{t_i}\) 离 \(x\) 最近,根据贪心的思想我们知道每一次操作要充分利用空间,所以一定要选择 \(x\),给 \(y\) 的话就可能会使后面一些更小的数放不进去,而放 \(x\) 一定不劣,因为 \(p_{t_{i-1}}\) 下之后最小的数只有 \(p_{t_i}\) 了,所有我们就证明了不会出现这样的操作。
于是我们最终的 \(x\) 就变成了前缀最大值组成的数列,而 \(y\) 呢?注意到我们之前说不是前缀最大值的数可以待定,相当想选什么就选什么,因为如果不选可以交给 \(x\) 做一次无效操作嘛。而我们要求最多操作次数,也就使最长上升子序列,但是 \(\mathcal O(n^2)\) 会超时,我们可以使用贪心加二分的经典做法也可以选择 BIT。最终答案为前缀最大值的个数加上非前缀最大值构成数列的最长上升子序列长度,时间复杂度为 \(\mathcal O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5+10;
int n,p[N],b[N],q[N];
signed main(){
cin>>n;
for(int i=1;i<=n;++i)cin>>p[i];
int cnt=0;
int pre=p[1],ans=1;
for(int i=2;i<=n;++i){
if(p[i]>pre){
pre=p[i];
++ans;
}else{
b[++cnt]=p[i];
}
}
int tot=0;
if(b[1])q[++tot]=b[1];
for(int i=2;i<=cnt;i++){
if(b[i]>q[tot]){
q[++tot]=b[i];
}else{
int l=0,r=tot+1;
while(l<r-1){
int mid=(l+r)>>1;
if(b[i]<q[mid]) r=mid;
else l=mid;
}
q[r]=b[i];
}
}
cout<<ans+tot;
return 0;
}
笑点分析:20 行的特判我 1h 都没发现。
浙公网安备 33010602011771号