过分的神圣,往往比恶魔更加恶质。

前言

最大的一个收获就是不要动不动就码线段树,一定要审清楚题目之后再码!!

T1 一开始理解错题了,以为答案是就是 \(\dfrac{\operatorname{len}(s,t)}{k}\) 然后就傻呵呵地码完了,然后看了看整个机房都没有什么动静,才发现自己的无知。

后来就是换做法,以为是 线段树+树链剖分 ,想了好久的区间合并,最后想的差不多了,就开始码线段树,刚刚码完就发现直接 前缀和+lower_bound 就可以解决这个问题,主要是这个思路还是错的。。。

比较成功的就是 20min 码完了 T3 的小暴力,然后稍微调了两下就过了。。

T1 第零题

解题思路

有一个结论:

对于⼀条链,如果体⼒都是满的从两边开始⾛,那么复活的次数是一样的

证明的话,这里只证明一下比较难的部分:一条全部由 \(<k\) 的价值构成的链。

假设现在有一条总和在 \((k,2k)\) 之间的链,并且满足上述条件,那么加入说我们把起始点向另一端移一条边(保证链长依旧大于 k ),那么最后的复活次数还是 1 。

同样的,扩展到一个总和未知的链上,它也一定可以分成一段 \(<k\) 的和若干的长度 \(\ge k\) 的段,通过上述操作最终的复活次数还是一样的。

那么我们就可以以 LCA 为中心,对于这条链的左右两半部分分别向两端移动,最后计算中间的部分。

然后就可以通过倍增进行维护。

对于一个点记录这个点到根节点上的路径的各个点到根节点的距离以及对应的序号,然后在这个数组上二分就可以得到从这个点满状态开始向上的第一个死亡点了。

倍增数组维护就不用说了吧。。

然后对于起点 s 直接向上倍增跳复活数组,最后得到的就是到 LCA 的距离最大且距离 \(<k\) 的点。

接下来在 t 到 LCA 的那一条链上跳祖先直到到 LCA 的距离与前面求的剩余距离之和 \(\ge k\) 的点就是我们要移动的那一段,判断这一段是否 \(\ge k\) 就好了。

对于 t 到 LCA 链上剩下的点操作和 s 的那条链相差无几。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=2e5+10;
int n,m,q,top,sta[N],st[N],s[N],f[N][25];
int tot=1,head[N],ver[N<<1],nxt[N<<1],edge[N<<1];
int tim,dfn[N],id[N],son[N],siz[N],dep[N],dis[N],topp[N],fa[N];
int die[N][25];
void add_edge(int x,int y,int val)
{
ver[++tot]=y;
edge[tot]=val;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs1(int x)
{
siz[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
if(siz[to]) continue;
dis[to]=dis[x]+edge[i];
dep[to]=dep[x]+1;
fa[to]=x; s[to]=edge[i];
f[to][0]=x;
dfs1(to);
siz[x]+=siz[to];
if(siz[to]>siz[son[x]])
son[x]=to;
}
}
void dfs2(int x,int tp)
{
dfn[x]=++tim;
id[tim]=x;
topp[x]=tp;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=nxt[i])
if(!dfn[ver[i]])
dfs2(ver[i],ver[i]);
}
int LCA(int x,int y)
{
while(topp[x]^topp[y])
{
if(dep[topp[x]]<dep[topp[y]])
swap(x,y);
x=fa[topp[x]];
}
if(dep[x]>dep[y])
swap(x,y);
return x;
}
void dfs3(int x)
{
if(dis[x]-dis[fa[x]]>=m) die[x][0]=fa[x];
else
{
int pos=upper_bound(sta+1,sta+top+1,dis[x]-m)-sta-1;
if(dis[x]-sta[pos]<m) die[x][0]=0;
else die[x][0]=st[pos];
}
sta[++top]=dis[x]; st[top]=x;
for (int i=0;die[x][i];i++)
die[x][i+1]=die[die[x][i]][i];
for (int i=0;f[x][i];i++)
f[x][i+1]=f[f[x][i]][i];
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
if(to==fa[x]) continue;
dfs3(to);
}
top--;
}
signed main()
{
n=read(); m=read();
for(int i=1,x,y,val;i<n;i++)
x=read(),y=read(),val=read(),
add_edge(x,y,val),add_edge(y,x,val);
dfs1(1); dfs2(1,1); dfs3(1);
q=read();
while(q--)
{
int x,y,lca,val,temp,ans=0;
x=read(); temp=y=read(); lca=LCA(x,y);
for(int i=20;i>=0;i--)
if(die[x][i]&&dep[die[x][i]]>=dep[lca])
x=die[x][i],ans+=(1ll<<i);
val=dis[x]-dis[lca];
for(int i=20;i>=0;i--)
if(f[temp][i]&&dis[f[temp][i]]-dis[lca]>=m-val)
temp=f[temp][i];
if(dis[temp]+dis[x]-2*dis[lca]>=m) ans++;
for(int i=20;i>=0;i--)
if(die[y][i]&&dep[die[y][i]]>=dep[temp])
y=die[y][i],ans+=(1ll<<i);
printf("%lld\n",ans);
}
return 0;
}

T2 第负一题

解题思路

官方题解又双叒叕不说人话。。。

对于一个区间 \([l,r]\) 我们显然可以运用类似于 没有上司的舞会 的思路 \(\mathcal{O}(r-l+1)\) 求出来。

DP 转移柿子就是 \(f_{i,0}=\max(f_{i-1,0},f_{i-1,1}),f_{i,1}=f_{i-1,0}+s_i\)

对于正解的话,二分

对于每一个二分到的区间分成 \(L,R\) 两个部分,对于每半个区间维护两个值,\(fl_{i,0/1},fr_{i,0/1}\)

\(fl_{i,0}\) 表示默认不选择 \(mid\) 这个元素的在 \([i,mid]\) 的范围内可以得到的最大值。

\(fl_{i,1}\) 表示默认选择 \(mid\) 这个元素的在 \([i,mid]\) 的范围内可以得到的最大值。

\(fr\) 数组的定义类似,只不过范围是 \([mid+1,i]\)

然后对于位于 \(L\) 的 \(i\) 以及位于 \(R\) 的 \(j\),在 \([i,j]\) 区间的最大值就是:

\[\max\{fl_{i,0}+fr_{j,0},fl_{i,1}+fr_{j,0},fl_{i,0}+fr_{j,1}\}
\]

那么我们假设 \(ld_i=\max(0,fl_{i,1}-fl_{i,0}),rd_i=\max(0,fr_{i,1}-fr_{i,0})\)

然后对于区间 \([i,j]\) 的答案就是 \(fl_{i,0}+fr_{j,0}+\max(ld_i,rd_j)\)

每个 \(ld_i\) 以及 \(rd_j\) 的贡献就可以直接通过乘上另一半的区间长度来算。

那么问题就变为了求 \(\sum\limits_{i=l}^{mid}\sum\limits_{j=mid+1}^r \max(ld_i,rd_j)\)

我们先将 \(ld,rd\) 数组从小到大进行排序,扫描 \(L\) 部分,另外用一个指针 \(pos\) 维护 \(R\) 部分。

保证 \([mid+1,pos-1]\) 都是小于 \(ld_i\) 的,由此可以算出每一个 \(ld_i\) 的贡献。

在维护 \(pos\) 指针的同时统计当前的 \(rd_{pos}\) 的贡献,计算方式类似于上面的 \(ld_i\) 。

最后不要忘了把指针一直算到右端点就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=2e5+10,mod=998244353,INF=1e18;
int n,ans,s[N],fl[N][2],fr[N][2],f[N][2],ld[N],rd[N];
void Binary(int l,int r)
{
if(l==r) return ans=(ans+s[l])%mod,void();
int mid=(l+r)>>1,pos=mid+1;
Binary(l,mid); Binary(mid+1,r); f[mid][0]=-INF; f[mid][1]=s[mid]; fl[mid][1]=s[mid];
for(int i=mid-1;i>=l;i--)
{
f[i][0]=max(f[i+1][0],f[i+1][1]);
f[i][1]=f[i+1][0]+s[i];
fl[i][1]=max(f[i][0],f[i][1]);
} f[mid][0]=0; f[mid][1]=-INF; fl[mid][0]=0;
for(int i=mid-1;i>=l;i--)
{
f[i][0]=max(f[i+1][0],f[i+1][1]);
f[i][1]=f[i+1][0]+s[i];
fl[i][0]=max(f[i][0],f[i][1]);
} for(int i=mid;i>=l;i--)
{
ld[i]=max(0ll,fl[i][1]-fl[i][0]);
ans=(ans+fl[i][0]*(r-mid))%mod;
} f[mid+1][0]=-INF; f[mid+1][1]=s[mid+1]; fr[mid+1][1]=s[mid+1];
for(int i=mid+2;i<=r;i++)
{
f[i][0]=max(f[i-1][0],f[i-1][1]);
f[i][1]=f[i-1][0]+s[i];
fr[i][1]=max(f[i][1],f[i][0]);
} f[mid+1][0]=0; f[mid+1][1]=-INF; fr[mid+1][0]=0;
for(int i=mid+2;i<=r;i++)
{
f[i][0]=max(f[i-1][0],f[i-1][1]);
f[i][1]=f[i-1][0]+s[i];
fr[i][0]=max(f[i][1],f[i][0]);
} for(int i=mid+1;i<=r;i++)
{
rd[i]=max(0ll,fr[i][1]-fr[i][0]);
ans=(ans+fr[i][0]*(mid-l+1))%mod;
} sort(ld+l,ld+mid+1); sort(rd+mid+1,rd+r+1);
for(int i=l;i<=mid;i++)
{
while(pos<=r&&rd[pos]<=ld[i]) ans=(ans+rd[pos]*(i-l))%mod,pos++;
ans=(ans+(pos-mid-1)*ld[i])%mod;
}
while(pos<=r)ans=(ans+rd[pos]*(mid-l+1))%mod,pos++;
}
signed main()
{
n=read(); for(int i=1;i<=n;i++) s[i]=read();
Binary(1,n); printf("%lld",ans);
return 0;
}

T3 第负二题

解题思路

打了一个 \(\mathcal{O}(n^2)\) 的假做法,被 zero4338 Hack 了。

现在正在努力地学习 \(\mathcal{O}(n)\) 做法。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=5e6+10,mod=998244353;
int n,m,tim,cnt,out,L,X,Y,Ans;
ull A,B;
int l[N],r[N],lasl[N],lasr[N],ans[N];
bool las[N];
ull xorshift128p() {
ull T = A, S = B;
A = S;
T ^= T << 23;
T ^= T >> 17;
T ^= S ^ (S >> 26);
B = T;
return T + S;
}
void init() {
for (int i = 1; i <= n; i ++) {
l[i] = xorshift128p() % L + X;
r[i] = xorshift128p() % L + Y;
if (l[i] > r[i]) swap(l[i], r[i]);
}
}
signed main()
{
cnt=n=read();L=read();X=read();Y=read();scanf("%llu%llu",&A,&B);init();
las[0]=las[n+1]=true;
for(int i=1;i<=n/2+(n&1)&&cnt;i++)
{
for(int j=i-1;j<=n-i+2;j++)
if(!ans[j]) lasl[j]=l[j],lasr[j]=r[j];
else las[j]=true;
for(int j=i;j<=n-i+1;j++)
{
if(ans[j]) continue;
if(las[j-1]||las[j+1]){ans[j]=i;cnt--;continue;}
l[j]=max(l[j]+1,max(lasl[j-1],lasl[j+1]));
r[j]=min(r[j]-1,min(lasr[j-1],lasr[j+1]));
if(l[j]>r[j]) ans[j]=i,cnt--;
}
}
for(int i=1,base=1;i<=n;i++,base=base*3%mod)
Ans=(Ans+base*ans[i]%mod)%mod;
printf("%lld",Ans);
return 0;
}

NOIP模拟50的更多相关文章

  1. Noip模拟50 2021.9.10

    已经好长时间没有考试不挂分的良好体验了... T1 第零题 开场数据结构,真爽 对于这道题首先要理解对于一条链从上向下和从下向上走复活次数相等 (这可能需要晚上躺在被窝里面脑摸几种情况的样例) 然后就 ...

  2. 2021.9.9考试总结[NOIP模拟50]

    T1 第零题 神秘结论:从一个点满体力到另一个点的复活次数与倒过来相同. 于是预处理出每个点向上走第$2^i$个死亡点的位置,具体实现可以倍增或二分. 每次询问先从两个点同时向上倍增,都转到离$LCA ...

  3. NOIP模拟赛20161022

    NOIP模拟赛2016-10-22 题目名 东风谷早苗 西行寺幽幽子 琪露诺 上白泽慧音 源文件 robot.cpp/c/pas spring.cpp/c/pas iceroad.cpp/c/pas ...

  4. CH Round #49 - Streaming #4 (NOIP模拟赛Day2)

    A.二叉树的的根 题目:http://www.contesthunter.org/contest/CH%20Round%20%2349%20-%20Streaming%20%234%20(NOIP 模 ...

  5. 11.7 NOIP模拟赛

    目录 2018.11.7 NOIP模拟 A 序列sequence(two pointers) B 锁lock(思路) C 正方形square(埃氏筛) 考试代码 B C 2018.11.7 NOIP模 ...

  6. NOIP模拟赛-2018.11.7

    NOIP模拟赛 如果用命令行编译程序可以发现没加头文件之类的错误. 如果用命令行编译程序可以发现没加头文件之类的错误. 如果用命令行编译程序可以发现没加头文件之类的错误. 编译之前另存一份,听说如果敲 ...

  7. NOIP模拟题汇总(加厚版)

    \(NOIP\)模拟题汇总(加厚版) T1 string 描述 有一个仅由 '0' 和 '1' 组成的字符串 \(A\),可以对其执行下列两个操作: 删除 \(A\)中的第一个字符: 若 \(A\)中 ...

  8. 2016-06-19 NOIP模拟赛

          2016-06-19 NOIP模拟赛 by coolyangzc 共3道题目,时间3小时 题目名 高级打字机 不等数列 经营与开发 源文件 type.cpp/c/pas num.cpp/c ...

  9. 2014-10-31 NOIP模拟赛

        10.30 NOIp  模拟赛   时间 空间 测试点 评测方式 挖掘机(dig.*) 1s 256M 10 传统 黑红树(brtree.*) 2s 256M 10 传统 藏宝图(treas. ...

随机推荐

  1. 看视频学Bootstrap—在微软虚拟学院学习Bootstrap

    Bootstrap 是目前最流行的 HTML.CSS 和 JS 框架,用于开发响应式布局.移动设备优先的 WEB项目. 如果您希望在几个小时内对Bootstrap有一个直观的了解,观看微软虚拟学院(M ...

  2. Python实用案例,Python脚本,Python实现帮你选择双色球号码

    往期回顾 Python实现自动监测Github项目并打开网页 Python实现文件自动归类 前言: 今天我们就利用python脚本实现帮你选择双色球号码.直接开整~ 开发工具: python版本: 3 ...

  3. 手撸一个SpringBoot-Starter

    1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...

  4. 大数据学习(06)——Ozone介绍

    前面几篇文章把Hadoop常用的模块都学习了,剩下一个新模块Ozone,截止到今天最新版本是0.5.0Beta,还没出正式版.好在官方网站有文档,还是中文版的,但是中文版资料没有翻译完整,我试着把它都 ...

  5. CSS样式逐li添加,执行完,清空,反复执行

    function change_light(el) { el.hide() let i = 0; function temp() { if (i > el.length - 1) { el.hi ...

  6. Jetpack MVVM 实战项目,附带源码+视频,收藏!

    从读者的反馈来看,近期大部分安卓开发已跳出舒适圈,开始尝试认识和应用 Jetpack MVVM 到实际的项目开发中. 只可惜,关于 Jetpack MVVM,网上多是 东拼西凑.人云亦云.通篇贴代码  ...

  7. 线程创建的三种方法:继承Thread类,实现Runnable接口,实现Callable接口

    线程创建 三种创建方式 1. 继承Thread类 自定义线程类继承Thread类 重写run()方法,编写线程执行体 创建线程对象,调用start()方法启动线程 线程不一定执行,CPU按排调度 pa ...

  8. OpenResty Lua钩子调用完整流程

    前面一篇文章介绍了Openresty Lua协程调度机制,主要关心的是核心调度函数run_thread()内部发生的事情,而对于外部的事情我们并没有涉及.本篇作为其姊妹篇,准备补上剩余的部分.本篇将通 ...

  9. 基于AOP和HashMap原理学习,开发Mysql分库分表路由组件!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 什么?Java 面试就像造火箭 单纯了! 以前我也一直想 Java 面试就好好面试呗 ...

  10. SpringBoot开发十八-显示评论

    需求介绍 显示评论,还是我们之前做的流程. 数据层:根据实体查询一页的评论数据,以及根据实体查询评论的数量 业务层:处理查询评论的业务,处理查询评论数量的业务 表现层:同时显示帖子详情数据时显示该帖子 ...