N O I NOI NOI O n l i n e Online Online R o u n d Round Round 3 3 3 总结
这一次我分别考了提高组和入门组,题目不算很难。
注意:所有成绩都是官方测评的结果。
入门组:
提高组:
提高组
提高组的题目不算很难,第一题好像很简单,其他题目就难了。
第一题: k e t t l e kettle kettle
题目大意
有一个长度为 n n n序列 a a a,可以做 k k k个操作,每次操作时选择一个 i ( 1 ≤ i ≤ n − 1 ) i(1\leq i\leq n-1) i(1≤i≤n−1),将第 i + 1 i+1 i+1个数加上第 i i i个数,并将第 i i i个数清 0 0 0。
解题方法
这道题的解题方法是贪心。
其实我们可以发现每一次选择一段进行操作就行了,也就是求
max
i
=
k
+
1
n
∑
j
=
i
−
k
i
a
j
\begin{aligned}\max_{i=k+1}^{n}{\sum_{j=i-k}^{i}{a_j}}\end{aligned}
i=k+1maxnj=i−k∑iaj。
因为这是个静态序列,所以我们可以直接用前缀和优化。
设
s
i
=
∑
i
=
1
n
a
i
\begin{aligned}s_i=\sum_{i=1}^{n}{a_i}\end{aligned}
si=i=1∑nai,则
答案为
max
i
=
k
+
1
n
s
i
−
s
i
−
k
−
1
\begin{aligned}\max_{i=k+1}^{n}{s_i-s_{i-k-1}}\end{aligned}
i=k+1maxnsi−si−k−1。
时间复杂度为
O
(
n
)
O(n)
O(n)。
得分情况
比赛时满分。
代码
#include<bits/stdc++.h>
#define N 1000001
using namespace std;
int n,k,b[N],ans,f[N];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&b[i]),f[i]=f[i-1]+b[i];
for(int i=k+1;i<=n;i++) ans=max(ans,f[i]-f[i-k-1]);
printf("%d",ans);
}
第二题: m a g i c magic magic
题目大意
第
j
j
j天第
i
i
i个结点的魔法值为
f
p
1
,
j
−
1
⨁
f
p
2
,
j
−
1
⨁
f
p
3
,
j
−
1
⨁
.
.
.
⨁
f
p
m
,
j
−
1
f_{p_1,j-1}\bigoplus f_{p_2,j-1}\bigoplus f_{p_3,j-1}\bigoplus...\bigoplus f_{p_m,j-1}
fp1,j−1⨁fp2,j−1⨁fp3,j−1⨁...⨁fpm,j−1。
其中
p
p
p为所有与
i
i
i连接的结点。
给你
f
i
,
0
f_{i,0}
fi,0,求
f
1
,
a
f_{1,a}
f1,a。
解题方法
不懂,好像是矩阵乘法。
得分情况
比赛时 0 0 0分。
第三题: s e q u e n c e sequence sequence
题目大意
规定一个优秀子序列满足任意两个位置的值互相按位与的结果是
0
0
0,一个子序列的价值为所有元素之和加一与小于它的数里面有多少个互质的。
求所有最优子序列的价值之和。
解题方法
不懂,好像是动态规划和优化。
得分情况
比赛时 0 0 0分。
普及组
这次我普及组 A K AK AK了。
第一题: s a v e save save
题目大意
有 n n n个字符串,求每一个字符串包含多少个 s o s sos sos,并求出包含最多个数的字符串。
解题方法
直接模拟,可以先求出最大的次数,再与每个次数进行比较,如果相同,就输出这个。
得分情况
比赛时满分。
代码
#include<bits/stdc++.h>
using namespace std;
int n,c[101],la[101],lb[101],ans=0;
char a[101][201],b[101][201];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int l=1;
a[i][l]=getchar();
while(a[i][l]<'a'||a[i][l]>'z') a[i][l]=getchar();
while(a[i][l]>='a'&&a[i][l]<='z')
{
l++;
a[i][l]=getchar();
}
l--;
la[i]=l;
l=1;
b[i][l]=getchar();
while(b[i][l]<'a'||b[i][l]>'z') b[i][l]=getchar();
while(b[i][l]>='a'&&b[i][l]<='z')
{
l++;
b[i][l]=getchar();
}
l--;
lb[i]=l;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=lb[i]-2;j++)
if(b[i][j]=='s'&&b[i][j+1]=='o'&&b[i][j+2]=='s')
c[i]++;
for(int i=1;i<=n;i++) ans=max(ans,c[i]);
for(int i=1;i<=n;i++)
if(c[i]==ans)
{
for(int j=1;j<=la[i];j++) putchar(a[i][j]);
putchar(' ');
}
printf("\n%d",ans);
}
第二题: s t a r star star
题目大意
有一张图。
将大小相同的连通块合并成一个连通块,并求出最多有多少块连通块和最大的连通块有多大。
解题方法
每一次如果没走过 b f s bfs bfs或 d f s dfs dfs一次,然后用桶,最后看一下哪个最大即可。
得分情况
比赛时满分。
代码
#include<bits/stdc++.h>
#define N 1501
#define M 2500001
#define K 100001
using namespace std;
int n,m,a[N][N],bz[N][N],b,c[K],k,ans1=0,ans2=0,dx[8]={0,0,1,1,1,-1,-1,-1},dy[8]={1,-1,0,1,-1,0,1,-1};
void dfs(int x,int y)
{
b++;
bz[x][y]=1;
for(int i=0;i<8;i++)
{
int u=x+dx[i],v=y+dy[i];
if(u<1||u>n||v<1||v>m) continue;
if(bz[u][v]==1||a[u][v]==0) continue;
dfs(u,v);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
char x=getchar();
while(x!='*'&&x!='.') x=getchar();
if(x=='*') a[i][j]=1;
else a[i][j]=0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]==1&&bz[i][j]==0)
{
b=0;
dfs(i,j);
c[b]+=b;
}
for(int i=1;i<=100000;i++)
if(c[i]>0)
ans1++,ans2=max(ans2,c[i]);
printf("%d %d",ans1,ans2);
}
第三题: w a t c h watch watch
题目大意
有
m
m
m种纸币,每一种是
k
i
k_i
ki元,最多能买
a
i
a_i
ai次。
现在问你
t
i
t_i
ti是否可以用刚好被凑出。
解题方法
模板多重背包——是可行性背包问题。
暴力 d p dp dp
设
f
j
f_j
fj表示
j
j
j元是否能凑出(能为
1
1
1,不能为
0
0
0),当前到了第
i
i
i种纸币,
k
k
k为可取次数。
则
f
j
=
{
1
f
j
−
a
i
×
k
=
1
0
f
j
−
a
i
×
k
=
0
f_j=\begin{cases} 1&f_{j-a_i\times k}=1\\ 0&f_{j-a_i\times k}=0 \end{cases}
fj={10fj−ai×k=1fj−ai×k=0
预处理
f
0
=
1
f_0=1
f0=1。
时间复杂度为
O
(
∑
i
=
1
n
a
i
×
max
j
=
1
m
t
j
)
O(\begin{aligned}\sum_{i=1}^{n}{a_i\times\max_{j=1}^{m}{t_j}}\end{aligned})
O(i=1∑nai×j=1maxmtj)。
可以过
50
50
50分。
二进制优化
很简单,直接把每一个数分解成二进制的数就行了。
可以过满分。
单调队列优化
本蒟蒻不会!
请大佬谅解。
可以过满分。
玄学做法
我的做法就是这个。
因为可行性,所有没有必要把所有都枚举一遍。
设
f
j
f_j
fj表示是否可行:如果
f
j
≥
0
f_j\geq0
fj≥0,则可行;否则不可行。
首先我们将所有
f
j
f_j
fj初始化为
−
1
-1
−1。
可以得到公式,
f
j
=
{
b
i
f
j
≥
0
−
1
j
<
a
i
−
1
f
j
−
a
i
≤
0
f
j
−
a
i
−
1
o
t
h
e
r
s
f_j=\begin{cases} b_i&f_j\geq0\\ -1&j<a_i\\ -1&f_{j-a_i}\leq0\\ f_{j-a_i}-1&others \end{cases}
fj=⎩⎪⎪⎪⎨⎪⎪⎪⎧bi−1−1fj−ai−1fj≥0j<aifj−ai≤0others
注:上面的
o
t
h
e
r
s
others
others指都不符合上面
3
3
3的情况。
时间复杂度为
O
(
n
max
i
=
1
m
t
i
)
O(\begin{aligned}n\max_{i=1}^{m}{t_i}\end{aligned})
O(ni=1maxmti)。
可以过满分。
发现:玄学方法比其他方法快。
得分情况
比赛时满分。
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,a[201],b[201],c[100001],f[500001],k;
int main()
{
scanf("%d%d",&n,&m);
f[0]=1;
for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
for(int i=1;i<=m;i++)
{
scanf("%d",&c[i]);
k=max(c[i],k);
}
for(int i=1;i<=k;i++) f[i]=-1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
if(f[j]>=0) f[j]=b[i];
else if(j<a[i]||f[j-a[i]]<=0) f[j]=-1;
else f[j]=f[j-a[i]]-1;
}
}
for(int i=1;i<=m;i++)
if(f[c[i]]>=0) printf("Yes\n");
else printf("No\n");
}