文章目录
第一场
这一场比较简单,与普转提的小朋友们一起打的。
Edit
签到题,字符串模拟即可。
Game
一个一眼看上去非常贪心的题目,但贪心只能过样例 。本题贪心也可以做,但需要判断负数个数的奇偶性,还有一些细节,哪有直接
d
p
dp
dp 来的快呢?
不难发现,将
a
a
a 从大到小排序后,
A
l
i
c
e
Alice
Alice 选择的数显然是一段区间,所以相当于在一段区间中扣掉几个不连续的数求最大和。显然可以设
f
[
i
]
f[i]
f[i] 为前
i
i
i 个数中最大和。
转移方程:
f
[
i
]
=
m
a
x
(
f
[
i
−
1
]
,
f
[
i
−
2
]
)
+
a
[
i
]
f[i]=max(f[i-1],f[i-2])+a[i]
f[i]=max(f[i−1],f[i−2])+a[i]
初始状态:
f
[
1
]
=
a
[
1
]
,
f
[
2
]
=
a
[
1
]
+
a
[
2
]
f[1]=a[1],f[2]=a[1]+a[2]
f[1]=a[1],f[2]=a[1]+a[2]
目标:
f
[
n
]
f[n]
f[n]
Score
考场上没有做出来的一道题。
50
50
50 分:
显然直接枚举
l
,
r
l,r
l,r 暴力统计即可。
20
20
20 性质分:
不难观察到对于每一段需要使平均数为整数,每个数要么全是
1
1
1 ,要么全是
2
2
2 。直接找
1
,
2
1,2
1,2 的连续段然后统计即可。
满分:
观察到
a
i
a_i
ai 的取值范围很小,进而想到平均数的范围也很小,所以以平均数
s
c
o
sco
sco 作为阶段,考虑平均数等于
s
c
o
sco
sco 的区间个数。
套路的,我们对每一个数减去
s
c
o
sco
sco 然后计算前缀和,一个区间
[
l
,
r
]
[l,r]
[l,r] 的平均数等于
s
c
o
sco
sco 当且仅当
s
u
m
[
l
−
1
]
=
s
u
m
[
r
]
sum[l-1]=sum[r]
sum[l−1]=sum[r] ,开个桶从头到尾扫一遍即可。
#include<bits/stdc++.h>
using namespace std;
const int INF=10000000;
int n,a[200000],b[200000],t[20000000];
long long ans;
int main()
{
freopen("score.in","r",stdin);
freopen("score.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=100;i++)
{
for(int j=1;j<=n;j++)
b[j]=a[j]-i;
t[INF]=1;
int sum=0;
for(int j=1;j<=n;j++)
{
sum+=b[j];
ans+=t[INF+sum];
t[INF+sum]++;
}
t[INF]=0;
sum=0;
for(int j=1;j<=n;j++)
{
sum+=b[j];
t[INF+sum]=0;
}
}
printf("%lld",ans);
return 0;
}
反思:对于平均数的这个套路有些许生疏了。
Seq
性质题。
假设
b
1
b_1
b1 为前缀最大值,那么后面的
b
i
b_i
bi 应为一个类单峰。先单调递减,后单调递增,否则不满足条件。为什么是类单峰?因为如果在后端上升的时候,如果有几个点不满足整体的上升趋势,但是他等于谷底的值,我们可以使他成为前缀最小值,然后给他附一个极大值,从而接受它的“不合群”。
因为我们钦定
b
1
b_1
b1 为前缀最小值,会缺少情况,所以我们再倒着做一遍即可。
City
一道码农题。
思路很简单,先并查集和必须城市群,然后从大到小慢慢合并,最后群内连边以及群外连边。
考场上
=
=
= 手误码成了
=
=
==
==,
t
o
t
tot
tot 写成了
n
n
n ,从
100
p
t
s
100pts
100pts 秒变
35
p
t
s
35pts
35pts
Q
A
Q
\quad QAQ
QAQ 。
反思:对于码农题,码的时候还是要耐心加细心。
总结:没啥可总结的 ,套路还是得常记,码农最怕不仔细。
第二场
这一场难度有所上升,但是总体上还比较简单,同样是与普转提的小盆友们一起打的。
最后一课
显然的将军饮马,但是考场上审题审错了,以为是 y = k x y=kx y=kx ,调了一个小时,最后才发现审错了。。。
反思:十年 OI 一场空,瞎审题目见祖宗。
日常
题解是二分的做法,考场上我写的离线。
把靶子按左端点排序,之后把询问给离线下来,这道题看似有
A
g
a
i
n
Again
Again ,不能离线,但转念一想只需要记录下哪个靶子就行,
A
g
a
i
n
Again
Again 在答案统计时自动就可以判出。
界外科学
这是之前模拟赛考过的一道题,真的是一眼折半搜索加二分,但是考场上写挂了。。。
不过讲正解之前我想先写点小插曲,考试结束后,这道题我得了八十分,因为写挂了嘛。但是我看见有很多人得到了一百分,我觉得很不合理,因为这道题也不算是特别简单,我是因为做过所以才很快有了思路,当时做这题的时候可把我折磨坏了 ,所以我看了看他们的代码,看完后震惊住了,他们居然
d
f
s
dfs
dfs 爆搜过了。
这题
n
n
n 的范围直接指数级爆搜肯定是过不去的,不过他们都加了一个神奇剪枝:
if(sum+num2[n]-num2[x]<=ans) return ;//a^b>=a当前不符合则说明无论如何xor都不符合。
仔细思考一下,这个剪枝其实相当强大,异或的不确定性直接用加法的确定性排除,再加上排完序后感性理解一下,价值小的一般要选,所以
d
f
s
dfs
dfs 的深度优先搜索的优势就被发挥出来了。我们用拍拿正解和这种做法拍了好长时间一直拍不出来
T
T
T 的数据。
但错解终究是错解,我们决定手动造一组数据来卡掉他。
h
a
c
k
hack
hack 数据:
36
1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
114514
1919810
54188
12345678
10086
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1000000000
36\,1\\ 2\,4\,8\,16\,32\,64\,128\,256\,512\,1024\,2048\,4096\,8192\,16384\,32768\,65536\\131072\,262144\,524288\,1048576 \,2097152\,4194304\,8388608\,16777216\\33554432\,67108864\,134217728\,268435456\,536870912\,114514\,1919810 \,54188\\12345678\,10086\,1\\ 1\,2\,3\,4\,5\,6\,7\,8\,9\,10\,11\,12\,13\,14\,15\,16\,17\,18\,19\,20\,21\,22\,23\,24\,25\,26\,27\,28\,29\,30\,31\,32\,33\,34\,35\, 1000000000
361248163264128256512102420484096819216384327686553613107226214452428810485762097152419430483886081677721633554432671088641342177282684354565368709121145141919810541881234567810086112345678910111213141516171819202122232425262728293031323334351000000000
构造思路:
考虑到深度优先搜索的优先性,我们把可行的答案放在最后,使其难以搜到。为了前面不被最优化剪枝减掉,我们把大小设为
2
2
2 的整次幂,这样保证了选取的全面性,这样就卡掉了
d
f
s
dfs
dfs 。
顺便一提,这题本来有一个随机化的做法,每次随机整个排列,直接选取前几个来更新答案,感性理解一下因为
n
n
n 很小,所以概率非常大。但是这组数据顺便也把随机化给卡掉了,可能是因为满足这个答案的情况比较少吧。
好了终于到正解了:
观察数据范围
n
≤
36
n\le36
n≤36,这个数据范围很小,但偏偏要比一般搜索大,那么考虑折半搜索。思考如何把中间的答案合并,因为是异或,自然而然的想到
01
t
r
i
e
01trie
01trie 树,考虑把一半答案插入
t
r
i
e
trie
trie 树中,并递归求解
m
a
x
max
max 数组。对于另一半答案,按位枚举,如果
m
m
m 当前位为
0
0
0 ,那么
t
r
i
e
trie
trie 树上只能跳转与
a
n
s
ans
ans 当前位相同的
t
r
i
e
trie
trie 树节点;如果
m
m
m 当前位为
1
1
1 ,那么
t
r
i
e
trie
trie 树上跳与
a
n
s
ans
ans 相同的节点一定合法,直接用
m
a
x
max
max 数组更新。然后考虑跳与
a
n
s
ans
ans 当前位相反的
t
r
i
e
trie
trie 树节点。如果跳的过程中该节点不存在,则直接返回。
#include<bits/stdc++.h>
using namespace std;
int n,m,a[50],b[50],trie[6000000][2],tot;
long long maxx[6000000],ans,en[6000000];
void insert(int x,long long y)
{
int p=0;
for(int i=31;~i;i--)
{
int now=(x>>i)&1;
if(!trie[p][now])
trie[p][now]=++tot;
p=trie[p][now];
maxx[p]=max(maxx[p],y);
}
en[p]=max(en[p],y);
}
long long ask(int x)
{
int p=0;
long long las=-1e18;
for(int i=31;~i;i--)
{
int p1=(x>>i)&1,p2=(m>>i)&1;
if(!p1)
{
if(!p2)
{
if(!trie[p][0])
return las;
p=trie[p][0];
}
else
{
if(trie[p][0])
las=max(las,maxx[trie[p][0]]);
if(!trie[p][1])
return las;
p=trie[p][1];
}
}
else
{
if(!p2)
{
if(!trie[p][1])
return las;
p=trie[p][1];
}
else
{
if(trie[p][1])
las=max(las,maxx[trie[p][1]]);
if(!trie[p][0])
return las;
p=trie[p][0];
}
}
}
return max(las,en[p]);
}
void dfs1(int x,int y,long long z)
{
if(x==n/2)
{
insert(y,z);
return;
}
dfs1(x+1,y^a[x+1],z+b[x+1]);
dfs1(x+1,y,z);
}
void dfs2(int x,int y,long long z)
{
if(x==n)
{
ans=max(ans,ask(y)+z);
return;
}
dfs2(x+1,y^a[x+1],z+b[x+1]);
dfs2(x+1,y,z);
}
void solve()
{
dfs1(0,0,0);
dfs2(n/2,0,0);
}
int main()
{
freopen("shop.in","r",stdin);
freopen("shop.out","w",stdout);
scanf("%d%d",&n,&m);
memset(maxx,0xcf,sizeof maxx);
memset(en,0xcf,sizeof en);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
solve();
printf("%lld",ans);
return 0;
}
树上小游戏
这是一道博弈论,因为没学过所以做起来很恶心,不过也不是完全不可做。
观察到状态只与层数有关,也就是说可能的状态只有
3
n
3^n
3n 种,可以直接
d
f
s
dfs
dfs 枚举。对于博弈论的部分,思考如果是叶子结点,那么判断是谁以及当前状态的奇偶性从而判断胜负,对于非叶子结点,如果存在状态后手必败,那么显然可以先手必胜,否则先手必败。
算一下时间复杂度,对于第i层的点,会dp n-i次,这一层有2^i个点
复杂度
O
(
∑
i
=
1
n
2
i
∗
3
n
−
i
)
O(\sum\limits _{i=1}^n 2^i*3^{n-i})
O(i=1∑n2i∗3n−i)。
#include<bits/stdc++.h>
using namespace std;
int n,l[33000],r[33000],c[33000];
vector<int> q;
bool dfs(vector<int> x,bool dep,bool num)
{
bool flag=num;
for(int i=0;i<x.size();i++)
flag^=c[x[i]];
vector<int> cc;
cc.clear();
if(l[x[0]])
{
for(int i=0;i<x.size();i++)
cc.push_back(l[x[i]]);
if(!dfs(cc,dep^1,flag))
return 1;
cc.clear();
for(int i=0;i<x.size();i++)
cc.push_back(r[x[i]]);
if(!dfs(cc,dep^1,flag))
return 1;
for(int i=0;i<x.size();i++)
cc.push_back(l[x[i]]);
if(!dfs(cc,dep^1,flag))
return 1;
return 0;
}
else
{
if(!flag&&!dep||flag&&dep)
return 0;
return 1;
}
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&l[i],&r[i],&c[i]);
for(int i=1;i<=n;i++)
{
q.clear();
q.push_back(i);
if(dfs(q,1,0))
puts("Alice");
else
puts("Bob");
}
return 0;
}
签到
暴力很好写,直接模拟即可。暴力之后打表可以发现这个排列的变化是有循环元的,所以考虑对于每一个位置,把他的变化建环,先算出循环元
s
s
s ,然后每一个位置考虑
m
m
o
d
s
m\,mod\,s
mmods,直接计算位置即可。
正解:
上面的做法时间复杂度是
O
(
n
l
g
m
)
O(nlgm)
O(nlgm),不足以通过此题,但是对于大小相同的环,走几步最后的落脚点是一样的,所有环的大小之和为
n
n
n ,所以环的种类只有
n
\sqrt n
n 种 这样时间复杂度就被优化到了
O
(
n
l
g
m
)
O(\sqrt nlgm)
O(nlgm) 但仍不足以通过本题,不过我们对于高精数
m
m
m 的存储没有必要按位存储,直接每
17
17
17 位用一个
l
o
n
g
l
o
n
g
long\,long
longlong 存储,这样复杂度就变为了
O
(
n
⋅
l
g
m
17
)
O(\sqrt n \cdot\frac{lgm}{17})
O(n⋅17lgm) 足以通过本题。
#include<bits/stdc++.h>
using namespace std;
int n,a[1000100],c[1000100],tot,nn,f[1000100],now,id[1000100],bignum;
char s[1000100];
vector<int> q[1000100];
__int128 num[1000100];
int ksm(int x,int y,int z)
{
int ans=1;
while(y)
{
if(y&1)
ans=((long long)ans*x)%z;
x=(long long)x*x%z;
y>>=1;
}
return ans%z;
}
int Mod(int x)
{
int ff=0,lx=1;
bignum=ksm(10,36,x);
for(int i=1;i<=now;i++)
{
ff=(ff+num[i]%x*lx%x)%x;
lx=(long long)lx*bignum%x;
}
return ff;
}
int main()
{
freopen("pow.in","r",stdin);
freopen("pow.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
if(c[a[i]])
continue;
int jl=a[a[i]],js=0;
c[a[i]]=++tot;
id[a[i]]=0;
q[tot].push_back(a[i]);
while(jl!=a[i])
{
c[jl]=tot;
id[jl]=++js;
q[tot].push_back(jl);
jl=a[jl];
}
}
scanf("%s",s+1);
nn=strlen(s+1);
for(int i=nn;i>0;i-=36)
{
__int128 jl=0;
for(int j=max(i-35,1);j<=i;j++)
jl=jl*10+s[j]-'0';
num[++now]=jl;
}
for(int i=1;i<=n;i++)
{
if(f[q[c[a[i]]].size()])
{
if(id[a[i]]+f[q[c[a[i]]].size()]>q[c[a[i]]].size())
printf("%d ",q[c[a[i]]][id[a[i]]+f[q[c[a[i]]].size()]-q[c[a[i]]].size()-1]);
else
printf("%d ",q[c[a[i]]][id[a[i]]+f[q[c[a[i]]].size()]-1]);
}
else
{
int ys=Mod(q[c[a[i]]].size());
if(!ys)
ys=q[c[a[i]]].size();
f[q[c[a[i]]].size()]=ys;
if(id[a[i]]+f[q[c[a[i]]].size()]>q[c[a[i]]].size())
printf("%d ",q[c[a[i]]][id[a[i]]+f[q[c[a[i]]].size()]-q[c[a[i]]].size()-1]);
else
printf("%d ",q[c[a[i]]][id[a[i]]+f[q[c[a[i]]].size()]-1]);
}
}
return 0;
}
总结:
这场考试时间分配严重失衡,大部分时间都放在了前三题,第三题还写挂了,还是得多注意时间。博弈论也要涉足一些了,高精不太熟练了,导致考场上都不太敢码。
第三场
这一场是多校联考,难度明显上升。
滑雪
签到题,倒序模拟即可。
农场道路修建
首先这题不管怎么样都得先做一遍最大点独立集。考试的时候没有往正解方向去想,光想着怎么当暴力哥了。最后码了个
32
p
t
s
32pts
32pts 的基环树(我真的很喜欢在考场上码一些我没写过的算法,嗯)。搞笑的是最后因为码的太丑,常数太大,只拿了
8
p
t
s
8pts
8pts。。。
正解:
思考到当且仅当两个必选点连边是不合法的,考虑如何算出必选点的点集。我们再来看一下最大点独立集的转移方程,其实救赎之道就在其中
f
[
i
]
[
0
]
=
∑
j
∈
s
o
n
[
i
]
m
a
x
(
f
[
j
]
[
0
]
,
f
[
j
]
[
1
]
)
f[i][0]=\sum\limits_{j\in son[i]}max(f[j][0],f[j][1])
f[i][0]=j∈son[i]∑max(f[j][0],f[j][1])
f
[
i
]
[
1
]
=
∑
j
∈
s
o
n
[
i
]
f
[
j
]
[
0
]
f[i][1]=\sum\limits_{j\in son[i]}f[j][0]
f[i][1]=j∈son[i]∑f[j][0]
如果
i
i
i 为必选点,那么
j
j
j 根据方程为必不选点。
如果
i
i
i 为必不选点,因为要保证最优性,那么
j
j
j 为
{
必选点
i
f
f
[
j
]
[
0
]
<
f
[
j
]
[
1
]
必不选点
i
f
f
[
j
]
[
0
]
>
f
[
j
]
[
1
]
无所谓点
i
f
f
[
j
]
[
0
]
=
f
[
j
]
[
1
]
\begin{cases}必选点&if\,f[j][0]<f[j][1]\\必不选点&if\,f[j][0]>f[j][1]\\无所谓点&if\,f[j][0]=f[j][1]\end{cases}
⎩
⎨
⎧必选点必不选点无所谓点iff[j][0]<f[j][1]iff[j][0]>f[j][1]iff[j][0]=f[j][1]
如果
i
i
i 为无所谓点,那么
j
j
j 为
{
必不选点
i
f
f
[
j
]
[
0
]
>
f
[
j
]
[
1
]
无所谓点
i
f
f
[
j
]
[
0
]
≤
f
[
j
]
[
1
]
\begin{cases}必不选点&if\,f[j][0]>f[j][1]\\无所谓点&if\,f[j][0]\le f[j][1]\end{cases}
{必不选点无所谓点iff[j][0]>f[j][1]iff[j][0]≤f[j][1]
初始状态 1 1 1 想像其虚拟父亲为必不选点来做即可。
#include<bits/stdc++.h>
using namespace std;
int n,head[300000],tot,f[300000][2],flag[300000],sum;
struct node
{
int to,nex;
} edge[600000];
void add(int x,int y)
{
edge[++tot].nex=head[x];
edge[tot].to=y;
head[x]=tot;
}
void dfs1(int x,int fa)
{
f[x][1]=1;
for(int i=head[x];i;i=edge[i].nex)
{
if(edge[i].to==fa)
continue;
dfs1(edge[i].to,x);
f[x][0]+=max(f[edge[i].to][0],f[edge[i].to][1]);
f[x][1]+=f[edge[i].to][0];
}
}
void dfs2(int x,int fa)
{
for(int i=head[x];i;i=edge[i].nex)
{
if(edge[i].to==fa)
continue;
if(flag[x]==1)
{
if(f[edge[i].to][0]>f[edge[i].to][1])
flag[edge[i].to]=1;
else if(f[edge[i].to][0]<f[edge[i].to][1])
{
flag[edge[i].to]=2;
sum++;
}
else
flag[edge[i].to]=3;
}
else if(flag[x]==2)
flag[edge[i].to]=1;
else
{
if(f[edge[i].to][0]>f[edge[i].to][1])
flag[edge[i].to]=1;
else
flag[edge[i].to]=3;
}
dfs2(edge[i].to,x);
}
}
int main()
{
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs1(1,0);
if(f[1][0]>f[1][1])
flag[1]=1;
else if(f[1][0]<f[1][1])
{
flag[1]=2;
sum++;
}
else
flag[1]=3;
dfs2(1,0);
printf("%lld",(long long)n*(n-1)/2-(long long)sum*(sum-1)/2);
return 0;
}
反思:考试的时候没有注意到这题的可做性,直接莽没有写过的算法,很不正确。对于把点分状态没有想到无所谓点如何处理,还得多加思考。
密码锁(坑)
这题暴力是直接
d
p
dp
dp,设
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示对于前
i
i
i 个字符,把第
i
i
i 个字符改成
j
j
j 所需的最小代价。
显然有
f
[
i
]
[
j
]
=
min
k
=
∣
a
∣
j
f
[
i
−
1
]
[
k
]
+
∣
j
−
k
∣
f[i][j]=\min\limits_{k=\left|a\right|}^{j}f[i-1][k]+\left|j-k\right|
f[i][j]=k=∣a∣minjf[i−1][k]+∣j−k∣
对于
52
p
t
s
52pts
52pts 其实我们可以换一种思路,因为他具有修改与查询操作,考虑线段树。但是我们现在设的状态并不适合线段树这种维护区间的数据结构,改一下,设
n
u
m
[
i
]
[
j
]
num[i][j]
num[i][j] 表示左端点为
i
i
i ,右端点为
j
j
j 的最小花费,这样子线段树直接暴力维护即可。
复杂度
O
(
q
l
o
g
n
⋅
5
4
)
O(q\,log\,n\cdot 5^4)
O(qlogn⋅54)
55
p
t
s
55pts
55pts 直接把暴力和线段树一分段即可。
#include<bits/stdc++.h>
using namespace std;
int q,id,f[100010][30],ans=0x3f3f3f3f,n;
char s[200000],c;
struct node
{
int l,r,num[5][5];
} tree[400000];
void build(int p,int l,int r)
{
tree[p].l=l;
tree[p].r=r;
if(l==r)
{
for(int i=0;i<5;i++)
for(int j=i;j<5;j++)
tree[p].num[i][j]=0x3f3f3f3f;
for(int i=0;i<5;i++)
tree[p].num[i][i]=abs(s[l]-'a'-i);
return;
}
int mid=(l+r)/2;
build(2*p,l,mid);
build(2*p+1,mid+1,r);
for(int i=0;i<5;i++)
for(int j=i;j<5;j++)
tree[p].num[i][j]=0x3f3f3f3f;
for(int i=0;i<5;i++)
{
for(int j=i;j<5;j++)
{
for(int k=j;k<5;k++)
{
for(int a=k;a<5;a++)
tree[p].num[i][a]=min(tree[p].num[i][a],tree[2*p].num[i][j]+tree[2*p+1].num[k][a]);
}
}
}
}
void change(int p,int x,char y)
{
if(tree[p].l==tree[p].r)
{
for(int i=0;i<5;i++)
for(int j=i;j<5;j++)
tree[p].num[i][j]=0x3f3f3f3f;
for(int i=0;i<5;i++)
tree[p].num[i][i]=abs(y-'a'-i);
return;
}
int mid=(tree[p].l+tree[p].r)/2;
if(x<=mid)
change(2*p,x,y);
else
change(2*p+1,x,y);
for(int i=0;i<5;i++)
for(int j=i;j<5;j++)
tree[p].num[i][j]=0x3f3f3f3f;
for(int i=0;i<5;i++)
{
for(int j=i;j<5;j++)
{
for(int k=j;k<5;k++)
{
for(int a=k;a<5;a++)
tree[p].num[i][a]=min(tree[p].num[i][a],tree[2*p].num[i][j]+tree[2*p+1].num[k][a]);
}
}
}
}
int ask()
{
int jl=0x3f3f3f3f;
for(int i=0;i<5;i++)
for(int j=i;j<5;j++)
jl=min(jl,tree[1].num[i][j]);
return jl;
}
int main()
{
freopen("lock.in","r",stdin);
freopen("lock.out","w",stdout);
scanf("%s%d",s+1,&q);
n=strlen(s+1);
if(q<=10)
{
memset(f,0x3f,sizeof f);
f[0][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<26;j++)
{
for(int k=0;k<=j;k++)
f[i][j]=min(f[i][j],f[i-1][k]+abs(s[i]-'a'-j));
if(i==n)
ans=min(ans,f[i][j]);
}
}
printf("%d\n",ans);
while(q--)
{
scanf("%d %c",&id,&c);
// cout<<id<<' '<<c<<endl;
s[id]=c;
memset(f,0x3f,sizeof f);
f[0][0]=0;
ans=0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
for(int j=0;j<26;j++)
{
for(int k=0;k<=j;k++)
f[i][j]=min(f[i][j],f[i-1][k]+abs(s[i]-'a'-j));
if(i==n)
ans=min(ans,f[i][j]);
}
}
printf("%d\n",ans);
}
}
else
{
build(1,1,n);
printf("%d\n",ask());
while(q--)
{
scanf("%d %c",&id,&c);
change(1,id,c);
printf("%d\n",ask());
}
}
return 0;
}
对于正解,是把字符串拆成 26 26 26 个 01 01 01 串进行求解,但是证明过程没有怎么看懂,感觉思路也不咋常用,先当一个坑吧。
奶牛打电话(坑)
考试时连暴力都没有写,现在重新看来随便瞎搞搞应该就能拿暴力分了。
正解是双指针加启发式合并,目前没啥订的欲望,先当做一个坑吧。
总结:这场比赛总体打的比较自闭,没有做出来 T 2 T2 T2 体验感非常差。调了大半小时的基环树还因为常数寄掉了。
第四场
这是目前的最后一场比赛了,好像是最难的,也是最自闭的。
分身乏术
二分加优先队列。
这题本质上不算是啥大难题,但是相比于其他的
T
1
T1
T1 也算是比较难了,考场上完全没有往二分上想是不应该的。想着直接
n
2
n^2
n2 贪心瞎搞搞还是寄掉了。
具体做法上没啥可总结的,思路上。。谨防贪心陷阱。
魔法是变化之神
这题没有写出来暴力是本场最大的失误。开始觉得题面很复杂,直接写了指数级的暴力,然后把时间直接全部砸在了其他题上,最后一分没有。
这题的暴力其实在我想出指数级暴力的时候就应该想到了。对于每一条边的删除实际上是相互独立的。那么我们完全可以把
m
m
m 当成容量,
s
i
z
e
u
size_u
sizeu 当成体积,
s
i
z
e
u
×
(
n
−
s
i
z
e
u
)
×
l
e
n
[
u
]
[
f
a
u
]
size_u\times(n-size_u)\times len[u][fa_u]
sizeu×(n−sizeu)×len[u][fau] 当成价值,跑一遍
01
01
01 背包即可。
对于正解,考虑到树是随机的
∑
s
i
z
e
[
x
]
=
O
(
n
l
o
g
n
)
\sum size[x]=O(n\,log\,n)
∑size[x]=O(nlogn),故只有最多
n
l
o
g
n
\sqrt {nlogn}
nlogn种不同的
s
i
z
e
x
size_x
sizex 所以跑二进制优化或者单调队列优化的多重背包即可。
#include<bits/stdc++.h>
using namespace std;
int n,m,head[200000],tot,now;
long long size[200000],V[200000],a[200000],f[600000],sum,maxx,cv[200000],ca[200000],c[200000];
map<pair<long long,long long>,int> q;
struct node
{
int to,nex,v;
} edge[500000];
void add(int x,int y,int z)
{
edge[++tot].nex=head[x];
edge[tot].to=y;
edge[tot].v=z;
head[x]=tot;
}
void dfs(int x,int fa)
{
size[x]=1;
for(int i=head[x];i;i=edge[i].nex)
{
if(edge[i].to==fa)
continue;
dfs(edge[i].to,x);
if(q.find(make_pair(size[edge[i].to],size[edge[i].to]*(n-size[edge[i].to])*edge[i].v))==q.end())
{
V[++tot]=size[edge[i].to];
a[tot]=size[edge[i].to]*(n-size[edge[i].to])*edge[i].v;
c[tot]=1;
q[make_pair(size[edge[i].to],size[edge[i].to]*(n-size[edge[i].to])*edge[i].v)]=tot;
}
else
c[q[make_pair(size[edge[i].to],size[edge[i].to]*(n-size[edge[i].to])*edge[i].v)]]++;
sum+=size[edge[i].to]*(n-size[edge[i].to])*edge[i].v;
size[x]+=size[edge[i].to];
}
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
tot=0;
dfs(1,0);
for(int i=1;i<=tot;i++)
{
long long lx=1;
while(c[i]>lx)
{
c[i]-=lx;
cv[++now]=lx*V[i];
ca[now]=lx*a[i];
lx*=2;
}
cv[++now]=c[i]*V[i];
ca[now]=c[i]*a[i];
}
for(int i=1;i<=now;i++)
{
for(int j=m;j>=cv[i];j--)
f[j]=max(f[j],f[j-cv[i]]+ca[i]);
}
for(int i=0;i<=m;i++)
maxx=max(maxx,f[i]);
printf("%lld",sum-maxx);
return 0;
}
反思:对于背包的敏感度一直比较低,还是得多加练习。
合并奶牛(坑)
暴力是一个简单的区间
d
p
dp
dp 。
对于正解是对这个区间
d
p
dp
dp 找性质进行疯狂的优化,差不多听懂讲解了,但暂时还不太想订。
旅行(坑)
正解是线段树分治加可持久化并查集。emm 都是我不太熟练的算法,还是先放放吧。
总结:要有一颗能发现突破点的双眼,不能因为哪个题面简单就单纯的把他当成突破口,说不定哪天考场上就又被坑了。