我相信大家可以感觉到DAY2题目的难度明显比DAY1大很多,这也是近年NOIP考试的趋势,从目前NOIP考察的知识来看这次的T3知识可能对NOIP选手来说略难,但说不定今年NOIP还会考更高级的算法,所以要有所防备。
再说一点,大家一定要注意部分分的获取。对于这套题,如果能力一般的同学采用部分分算法,理论至少上可以得到:100(预处理+动态规划)+40(只解决纯串联或并联情况)+55(采用暴力的“逐步爬山法”计算)=195分,对于DAY2来说是一个不错的分数。对于T2,个人觉得细节相当多,而且不知道“标程”是否正确。对于T3,得部分分难度不大,但它就是一个防NOIP选手AK的题,可以说,对大部分人(包括我)来说这道题写正解(200+ 行代码)还不如暴力(约100行代码)来得快。
最后,不要认为出题人能力比其他人强。因为出题没有比赛那么短的时间限制,所以可以花更多时间来解决自己的题。比如对于T2的第一个和第二个版本我自己都做不来,于是改为了现在的第三个版本,而且“标程”写错两次;对于T3我自己写了大约3个小时才写完正解。所以这次比赛200+的同学都是能力在我之上的。 ——于2018/10/14
当然题目方法不唯一,如果大家对DAY2的题目有更好的解法,欢迎提出!
比赛链接:NOIP2018原创模拟赛DAY2
考察知识:map,动态规划
算法难度:XXX+ 实现难度:XXX
说实话,我动态规划很弱,所以就出了一道不那么难的动态规划。
这道题还有更快的方法,请参考:洛谷 U40593 最后的战役(思维+离散化)
这道题贪心并不是完美解法,但是数据为随机生成的,所以得分概率非常大。
Hack数据:
4 2
1 1
2 3
3 6
4 8
1 2 3 4
标程输出:22 贪心输出:21
分析:
首先,我们要解决操作2,如果暴力枚举时间复杂度为。
其实吧,我们直接用map优化就可以了,时间复杂度:
for(int i=1;i<=n;i++) P[i]=max(P[i-1],p[i]);//求最大值
for(int i=1;i<=n;i++){
mp[k[i]]+=p[i];//在这里求和
P[i]=max(mp[x[i]],P[i]);
}
在处理了伏地魔在 [1,n] 每一步可以获得的最大魔法能量之后(记为P [ i ],记 为 SUM),我们就可以采取动态规划了:
定义:表示在[ 1 , i ] 秒中在 i 秒使用了魔法,且有 j 个时间段使用了魔法,可以得到的最大能量值
边界:
状态转移:
状态转移方程的实现还是要稍微处理一下,直接实现会超时:
for(int i=1;i<=n;i++)//处理边界
f[i][1]=SUM-P[i]+P[i+1],
ans=max(ans,f[i][1]);
for(int j=2;j<=m;j++){
for(int k=1;k<=n;k++) T[k]=max(T[k-1],f[k][j-1]);//预处理
for(int i=2*j-1;i<=n;i++)
f[i][j]=T[i-2]-P[i]+P[i+1],
ans=max(ans,f[i][j]);
}
好了,这道题我们就做出来了,理论时间复杂度:,但是时间效率不是很高,面对最大数据需要近900ms。
考察知识:图论,数学,推导,搜索
算法难度:XXXX 实现难度:XXXX
出题背景:我目前在学高中电学。最开始这道题搞得很难,我自己都做不来,于是不断降低难度:从混连->并联与并联和串联嵌套->并联只能嵌套串联。
分析:
这道题需要大家有一定的电学知识和数学推导能力。
先推导几个电学结论:
证明:易得,略
结论三:对于一个(等效)并联电路,电阻分别为,我们可以采取下面的算法计算等效电阻:
double R_=R[1];
for(int i=2;i<=n;i++) R_=R_*R[i]/(R_+R[i]);
ans=R_;
证明:结论二的二维形式:,每次将两个电阻合并为一个等效电阻,最后的值即为总等效电阻
结论四:对于一个(等效)并联电路,电阻分别为,其支路最小电流为:
证明:我们知道,最小电流在电阻最大的支路上,即,而电压为,由,就得到结论
有了这几个结论,我们就可以开始动手了:
1.先用结论三处理重边
2.然后bfs求出由电源正极到负极的一条路径
3.以这条路径为主线开始处理,遇到分叉边就表明此处有并联嵌套串联,用结论一+dfs计算嵌套串联的等效电阻,并记下,然后利用结论三进行并联电阻的合并
4.将所有等效电阻求和,即为整个电路的总等效电阻,如果主线电流为 则最小电流
代码实现细节请参考代码
考察知识:树链剖分,线段树,链表,排序,二分,字符串处理
算法难度:XXXXX 实现难度:XXXXX
分析:难题,算法复杂,代码量大!这是一道典型的考场上正解不如写暴力的题!
先介绍暴力算法(55分):
如果你的程序超时,你可以先尝试这道题:PION后缀自动机(数据弱化版)
算法难度:XXX 实现难度:XXX+
显然储存文件后缀信息不能用数组,空间开不下,所以我们用链表储存所有文件夹中文件的后缀信息;
然后是后缀的处理,注意到字符串长度小于6,如果我们用 1 表示 a ,2 表示 b ,... ... ,26 表示 z,这就相当于将字符串看做一个27进制数,这样我们就可以在 int 范围内用数字表示字符串了
解决了上面两个问题之后,我们就可以建树然后对每个操作用暴力的 “逐步爬山法” 解决了,实现难度不大
其实这种方法对随机数据效果还是比较好的(甚至比下面的满分算法还略快一点),但是你觉得数据可能完全随机么?
时间复杂度:极端情况约
满分算法:
代码量比较大。
看到这道题我们应该可以想到用树链剖分来做。
我们先考虑怎么对序列进行统计和修改,对于操作2,我们需要找到一个序列中的子序列。我们可以用一个结构体来储存,结构体储存序列中每个元素的值(字符串的hash值)和序列中每个元素在序列中的位置。然后将这个结构体序列按元素值的排序(元素值相同的按在序列中的位置排序)。当我们需要查找一个序列相同数值所在的区间的时候用 lower_bound 和 upper_bound 就可以完成,对于查找出来的目标区间,还需要用二分法来查找该区间中元素的下标在目标子序列中的数量。
至于修改,用线段树的区间维护实现。
解决了对序列的修改,我们还要解决树中每个节点含有的文件数目不相同的问题,如果一个节点没有文件,那么简单的方法是新建一个伪文件,可以将hash值赋为 -1。
之后就是树链剖分了,不同于树链剖分模板,我们还要新建一个辅助数组 low[i] ,表示节点 i 的重儿子的重儿子...(以此类推)可以到达的最深节点的编号,我们先对排序,之后查询的时候我们查询 之间的序列即可。
其实吧,上面只是代码实现的一部分,具体细节远不止这些,更多的细节请参考代码。
时间复杂度:极端情况约
(仅供参考)
T1:
#include
#include
#include
T2:
#include
T3:(暴力算法)
#include
#include
#include
using namespace std;
const int maxn=100005;
int next[maxn*5],hash[maxn*5],first[maxn],last[maxn],np_;
void add_file(int u,int num){//链表实现按位置储存文件
hash[++np_]=num;
if(first[u]) next[last[u]]=np_;
else first[u]=np_;
last[u]=np_;
}
int hash_num(char* s){
int ret=0;
for(int i=0;s[i]!=0;i++) ret=ret*27+s[i]-96;
return ret;
}
struct edge{//储存树边
int to,next;
}e[maxn*2];
int head[maxn],np;
void adde(int u,int v){//添加树边
e[++np]=(edge){v,head[u]};
head[u]=np;
e[++np]=(edge){u,head[v]};
head[v]=np;
}
int n,m,k[maxn],dep[maxn],fa[maxn];
void dfs(int i,int Fa){
fa[i]=Fa,dep[i]=dep[Fa]+1;
for(int p=head[i];p;p=e[p].next){
int j=e[p].to;
if(j==Fa) continue;
dfs(j,i);
}
}
void build(){
int u,v;
char ext_nm[10];
scanf("%d%d",&n,&m);
for(int i=1;i
T3:(正解)
#include
#include
#include
using namespace std;
const int maxn=100005;
int next[maxn*5],hash[maxn*5],first[maxn],last[maxn],np_;
void add_file(int u,int num){//链表实现按位置储存文件
hash[++np_]=num;
if(first[u]) next[last[u]]=np_;
else first[u]=np_;
last[u]=np_;
}
int hash_num(char* s){
int ret=0;
for(int i=0;s[i]!=0;i++) ret=ret*27+s[i]-96;
return ret;
}
struct edge{//储存树边
int to,next;
}e[maxn*2];
int head[maxn],np;
void adde(int u,int v){//添加树边
e[++np]=(edge){v,head[u]};
head[u]=np;
e[++np]=(edge){u,head[v]};
head[v]=np;
}
struct ext_name{
int v,id;
const bool operator < (const ext_name& B)const {
return v>1;
build(lc[now],l,M);
build(rc[now],M+1,r);
pushup(now);
}
void update(int now,int l,int r,int x,int y){
if(x<=l&&r<=y) {setv[now]=-1,sum[now]=0;return;}
int M=(l+r)>>1;
pushdown(now,l,r,M);
if(y<=M) update(lc[now],l,M,x,y);
else if(x>M) update(rc[now],M+1,r,x,y);
else update(lc[now],l,M,x,y),update(rc[now],M+1,r,x,y);
pushup(now);
}
int query(int now,int l,int r,int x,int y){
if(x<=l&&r<=y) return sum[now];
int M=(l+r)>>1;
pushdown(now,l,r,M);
if(y<=M) return query(lc[now],l,M,x,y);
else if(x>M) return query(rc[now],M+1,r,x,y);
else return query(lc[now],l,M,x,y)+query(rc[now],M+1,r,x,y);
}
/*-------------------树链剖分--------------------*/
int fa[maxn],son[maxn],dep[maxn],sz[maxn];
int P,top[maxn],seg[maxn],rev[maxn],low[maxn];
int lP[maxn],tP[maxn],pos;
void dfs1(int i,int Fa){
dep[i]=dep[Fa]+1,fa[i]=Fa,sz[i]=1;
for(int p=head[i];p;p=e[p].next){
int j=e[p].to;
if(j==Fa) continue;
dfs1(j,i);
sz[i]+=sz[j];
if(sz[son[i]]=R_) return 0;//没有找到
R_--;
int l_=R_+1,r_=L_-1;
L=L_,R=R_;
while(L<=R){//二分寻找
int M=(L+R)>>1;
if(D[M].id>=l) l_=M,R=M-1;
else L=M+1;
}
L=L_,R=R_;
while(L<=R){
int M=(L+R)>>1;
if(D[M].id<=r) r_=M,L=M+1;
else R=M-1;
}
if(l_>r_) return 0;
int TMP=query(rt,1,pos,l_,r_);//在线段树中查询
if(del) update(rt,1,pos,l_,r_);//删除文件
return TMP;
}
int query_path(int x,int y,int id,bool Access_y){//树链剖分中跳路径的方法
int fx=top[x],fy=top[y],ret=0;
while(fx!=fy){
ret+=calc(seg[fx],seg[x],seg[fx],seg[low[fx]],id,false);
x=fa[fx],fx=top[x];
}
if(Access_y) ret+=calc(seg[y],seg[x],seg[top[y]],seg[low[y]],id,false);
else ret+=calc(seg[y]+1,seg[x],seg[top[y]],seg[low[y]],id,false);
return ret;
}
int del_path(int x,int y,int id){
int fx=top[x],fy=top[y],ret=0;
while(fx!=fy){
ret+=calc(seg[fx],seg[x],seg[fx],seg[low[fx]],id,true);
x=fa[fx],fx=top[x];
}
ret+=calc(seg[y],seg[x],seg[top[y]],seg[low[y]],id,true);
return ret;
}
void build(){
int u,v;
char ext_nm[10];
scanf("%d%d",&n,&m);
for(int i=1;i