A题
传送门
题意:
一个人要做
n
n
n件事,时间花费分别为
a
1
,
a
2
,
.
.
.
,
a
n
,
a
1
≤
a
2
≤
a
3
≤
.
.
.
≤
a
n
a_1,a_2,...,a_n,a_1\le a_2\le a_3\le...\le a_n
a1,a2,...,an,a1≤a2≤a3≤...≤an
现在可以把至多
k
k
k件事分别花
x
x
x时间做完,问做完的最小总花费。
思路:按照题意模拟
代码:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
static char buf[rlen],*ib,*ob;
(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
return ib==ob?-1:*ib++;
}
inline int read(){
int ans=0;
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
return ans;
}
const int N=205;
int n,k,x,a[N];
int main(){
n=read(),k=read(),x=read();
for(ri i=1;i<=n;++i)a[i]=read();
int ans=0;
int pos=n-k;
for(ri i=n,cnt=1;i&&cnt<=k;--i,++cnt){
if(a[i]<=x){pos=i;break;}
ans+=x;
}
while(pos)ans+=a[pos--];
cout<<ans;
return 0;
}
B题
题意:
给一个长度为
n
n
n的
01
01
01串,问其最长的长度为偶数子串的
01
01
01数相等的长度。
思路:
设
0
0
0对应权值为
−
1
-1
−1,
1
1
1对应权值为
1
1
1,这样转化成求权值为
0
0
0的长度为偶数的子区间数。
这个可以用前缀和来看,如果前缀和相等说明中间夹的一段合法。
代码:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
return getchar();
static char buf[rlen],*ib,*ob;
(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
return ib==ob?-1:*ib++;
}
inline int read(){
int ans=0;
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
return ans;
}
const int N=1e5+5;
char s[N];
int n,a[N];
int pos[N<<1][2];
int main(){
n=read();
scanf("%s",s+1);
memset(pos,-1,sizeof(pos));
pos[100000][0]=0;
int ans=0;
for(ri sum=0,i=1;i<=n;++i){
sum+=s[i]=='0'?-1:1;
if(~pos[sum+100000][i&1])ans=max(ans,i-pos[sum+100000][i&1]);
else pos[sum+100000][i&1]=i;
}
cout<<ans;
return 0;
}
C题
传送门
题意:给一个
n
∗
m
n*m
n∗m的
01
01
01矩阵和一个数
k
k
k,现在对每一列以如下方式计算贡献:
- 若这一列没有 1 1 1,直接跳过。
- 如果有 1 1 1,找到最靠上的一个 1 1 1,然后贡献为从它开始向下连续至多 k k k个格子中 1 1 1的数量。
现在允许你把一些 1 1 1变成 0 0 0,问最后获得的贡献的最大值已经要使得贡献最大需要删掉格子的最小数量。
思路:对于每一列处理一个前缀和然后随便更新一下答案。
代码:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
static char buf[rlen],*ib,*ob;
(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
return ib==ob?-1:*ib++;
}
inline int read(){
int ans=0;
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
return ans;
}
const int N=105;
int n,m,k,a[N][N],sum[N][N],f[N][N];
int main(){
n=read(),m=read(),k=read();
for(ri i=1;i<=n;++i){
for(ri j=1;j<=m;++j){
a[i][j]=read(),sum[i][j]=a[i][j]+sum[i-1][j];
}
}
int ans1=0,ans2=0;
for(ri j=1;j<=m;++j){
if(!sum[n][j])continue;
int pre=0,pos=0;
for(ri i=n;i;--i){
if(!a[i][j])continue;
f[i][j]=sum[min(n,i+k-1)][j]-sum[i-1][j];
if(f[i][j]>=pre)pre=f[i][j],pos=i;
}
ans1+=pre,ans2+=sum[pos-1][j];
}
cout<<ans1<<' '<<ans2<<'\n';
return 0;
}
D题
传送门
题意:
现在有一个归并排序函数
m
e
r
g
e
s
o
r
t
(
l
,
r
)
mergesort(l,r)
mergesort(l,r)表示将
[
l
,
r
)
[l,r)
[l,r)位置的数给归并排序,如果已经有序会直接返回,否则会调用
m
e
r
g
e
s
o
r
t
(
l
,
m
i
d
)
mergesort(l,mid)
mergesort(l,mid)和
m
e
r
g
e
s
o
r
t
(
m
i
d
,
r
)
mergesort(mid,r)
mergesort(mid,r),现在给你
n
n
n和
k
k
k,要求构造出一个序列使得恰好调用
k
k
k次这个函数能够使得排序完成,无解输出
−
1
-1
−1。
思路:
首先将这个函数递归到的区间都看成结点,这就构成了一棵线段树,然后经过对题意的理解可以发现一个显然的结论:
k
k
k必须是一个奇数否则一定无解。
证明可以考虑数学归纳法。
然后我们一直递归左右区间,每次向下走都会产生
2
2
2的贡献,走不动了就返回即可。
代码:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
static char buf[rlen],*ib,*ob;
(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
return ib==ob?-1:*ib++;
}
inline int read(){
int ans=0;
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
return ans;
}
const int N=2e5+5;
int n,k,siz[N<<2],ans[N];
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (l+r>>1)
inline void build(int p,int l,int r){
siz[p]=1;
if(r-l<2)return;
build(lc,l,mid),build(rc,mid,r);
siz[p]=siz[lc]+1+siz[rc];
}
inline void solve(int p,int l,int r){
if(!k||r-l<2)return;
for(ri det=0;det+l<mid&&det+mid<r;++det)swap(ans[l+det],ans[mid+det]);
k-=2;
solve(lc,l,mid);
if(!k)return;
solve(rc,mid,r);
}
#undef lc
#undef rc
#undef mid
int main(){
n=read(),k=read();
if(!(k&1))return puts("-1"),0;
build(1,0,n);
if(k>siz[1])return puts("-1"),0;
if(k==1){
for(ri i=1;i<=n;++i)cout<<i<<' ';
return 0;
}
for(ri i=0;i<n;++i)ans[i]=i+1;
--k;
solve(1,0,n);
for(ri i=0;i<n;++i)cout<<ans[i]<<' ';
return 0;
}
E题
传送门
题意:有
n
(
n
≤
3000
)
n(n\le3000)
n(n≤3000)个人参与比赛,每个人都有一个解题数,现在要决定拿金牌的人数
c
n
t
1
cnt1
cnt1,拿银牌的人数
c
n
t
2
cnt_2
cnt2,拿铜牌的人数
c
n
t
3
cnt_3
cnt3,各自对应一个解题数区间
[
d
1
,
c
1
]
,
[
d
2
,
c
2
]
,
[
d
3
,
c
3
]
[d1,c1],[d2,c2],[d3,c3]
[d1,c1],[d2,c2],[d3,c3]
现在要求:
- d 1 − c 2 d1-c2 d1−c2最大化
- 满足第一点之后 d 2 − c 3 d2-c3 d2−c3最大化
- 满足前两点之后 d 3 − 胸 牌 第 一 名 解 题 数 d3-胸牌第一名解题数 d3−胸牌第一名解题数最大化
- 对于任意的x,y(1<=x,y<=3),cntx<=2*cnty
现在需要构造出一种合法的方案 1 , 2 , 3 , − 1 1,2,3,-1 1,2,3,−1表示金牌,银牌,铜牌和胸牌。
思路:
考虑枚举金牌线和银牌线。这样铜牌线的可行范围是一段连续区间,我们在差分数组上建立一个
s
t
st
st表取个极值即可。
代码:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
static char buf[rlen],*ib,*ob;
(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
return ib==ob?-1:*ib++;
}
inline int read(){
int ans=0;
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
return ans;
}
const int N=3005;
int diff[N],n,st[N][15],Log[N],p1,p2,p3,ans[N];
struct Node{
int a,id;
friend inline bool operator<(const Node&a,const Node&b){return a.a<b.a;}
}a[N];
int main(){
n=read();
for(ri i=1;i<=n;++i)a[i]=(Node){read(),i};
sort(a+1,a+n+1);
for(ri i=0;i<n;++i)diff[i]=a[i+1].a-a[i].a,st[i][0]=i;
p1=-1;
for(ri i=2;i<=n;++i)Log[i]=Log[i>>1]+1;
for(ri j=1;j<=12;++j)for(ri i=0;i<=n-(1<<j);++i)st[i][j]=diff[st[i][j-1]]>=diff[st[i+(1<<(j-1))][j-1]]?st[i][j-1]:st[i+(1<<(j-1))][j-1];
for(ri t1,t2,t3,l1,l2,i=(n+1)>>1;i<n;++i){
if(~p1&&diff[i]<diff[p1])continue;
t1=i,l1=n-i;
for(ri t,L,R,j=1;j<n;++j){
l2=i-j,t2=j;
if(2*l2<l1||2*l1<l2||j*2<l2||j*2<l1)continue;
L=(max(l1,l2)+1)/2,R=min(l1,l2)*2;
if(L>R)continue;
R=max(0,j-R),L=max(0,j-L),swap(L,R);
t=Log[R-L+1];
t3=(diff[st[L][t]]>=diff[st[R-(1<<t)+1][t]]?st[L][t]:st[R-(1<<t)+1][t]);
if(~p1){
if(diff[p1]>diff[t1])continue;
if(diff[p1]==diff[t1]&&diff[p2]>diff[t2])continue;
if(diff[p1]==diff[t1]&&diff[p2]==diff[t2]&&diff[p3]>diff[t3])continue;
}
p1=t1,p2=t2,p3=t3;
}
}
for(ri i=1;i<=p3;++i)ans[a[i].id]=-1;
for(ri i=p3+1;i<=p2;++i)ans[a[i].id]=3;
for(ri i=p2+1;i<=p1;++i)ans[a[i].id]=2;
for(ri i=p1+1;i<=n;++i)ans[a[i].id]=1;
for(ri i=1;i<=n;++i)cout<<ans[i]<<' ';
return 0;
}
F题
传送门
题意:给一个字符
t
t
t,上面有一些禁止位置,对于每一个子串
s
s
s,
f
(
s
)
f(s)
f(s)表示
s
s
s在
t
t
t中出现且结尾位置未被禁止的合法位置数,现在求
∣
s
∣
∗
f
(
s
)
|s|*f(s)
∣s∣∗f(s)最大值。
思路:
s
a
m
sam
sam模板题,直接套板子即可。
代码:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
return getchar();
static char buf[rlen],*ib,*ob;
(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
return ib==ob?-1:*ib++;
}
inline int read(){
int ans=0;
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
return ans;
}
const int N=2e5+5;
typedef long long ll;
char s[N],t[N];
int n;
namespace sam{
int len[N<<1],link[N<<1],son[N<<1][26],siz[N<<1],last=1,tot=1;
inline void expand(int x,int val){
int p=last,np=++tot;
siz[last=np]=val,len[np]=len[p]+1;
while(p&&!son[p][x])son[p][x]=np,p=link[p];
if(!p){link[np]=1;return;}
int q=son[p][x],nq;
if(len[q]==len[p]+1){link[np]=q;return;}
len[nq=++tot]=len[p]+1,memcpy(son[nq],son[q],sizeof(son[q])),link[nq]=link[q];
link[q]=link[np]=nq;
while(p&&son[p][x]==q)son[p][x]=nq,p=link[p];
}
inline void solve(){
static int cnt[N<<1],rk[N<<1];
ll ans=0;
for(ri i=1;i<=tot;++i)++cnt[len[i]];
for(ri i=1;i<=tot;++i)cnt[i]+=cnt[i-1];
for(ri i=1;i<=tot;++i)rk[cnt[len[i]]--]=i;
for(ri i=tot;i^1;--i)siz[link[rk[i]]]+=siz[rk[i]];
for(ri i=1;i<=tot;++i)ans=max(ans,(ll)len[i]*siz[i]);
cout<<ans;
}
}
int main(){
n=read();
scanf("%s%s",s+1,t+1);
for(ri i=1;i<=n;++i)sam::expand(s[i]-'a',t[i]=='1'?0:1);
sam::solve();
return 0;
}