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

前言

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

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. Nexus Repository Manager 3 远程命令执行漏洞(CVE-2020-10199、CVE-2020-10204)

    [影响版本] Nexus Repository Manager OSS/Pro 3.x <= 3.21.1 poc地址 https://github.com/magicming200/CVE-2 ...

  2. XSS challenges靶机

    第一关 <script>alert(123)</script> "><svg onload=alert(123)> 第二关 <script&g ...

  3. Kubernetes的基本概念和术语

    Kubernetes中的大部分概念如Node.Pod.Replication Controller. Service等都可以被看作一种资源对象,几乎所有资源对象都可以通过 Kubernetes提供的k ...

  4. Java 反射(二)运行时获取类的信息

    目录 一.获得类的运行时结构 1. 获得类的名字 2. 获得类的属性 获取属性列表 获取指定属性 3. 获取类的方法 获得类的方法列表 获得指定方法 4. 获得的构造器 获得构造器列表 获得指定构造器 ...

  5. python中map()和reduce()的使用

    map() 会根据提供的函数对指定序列做映射.map(function, iterable, ...)Python 3.x 返回迭代器.print(map()) 返回迭代器地址一般和list一起用 才 ...

  6. 第7篇-为Java方法创建栈帧

    在 第6篇-Java方法新栈帧的创建 介绍过局部变量表的创建,创建完成后的栈帧状态如下图所示. 各个寄存器的状态如下所示. // %rax寄存器中存储的是返回地址 rax: return addres ...

  7. 详细讲讲netty的pipiline!

    前言 提到 Netty 首当其冲被提起的肯定是支持它承受高并发的线程模型,说到线程模型就不得不提到 NioEventLoopGroup 这个线程池,接下来进入正题. 线程模型 首先来看一段 Netty ...

  8. 深入理解HTTP请求流程

    以前写过一篇博客,叫做HTTP的报文分析:https://blog.csdn.net/ZripenYe/article/details/119593269但是感觉还是不太深入.不够全面,顶多了解个大概 ...

  9. VLAN-4 单臂路由实现vlan之间路由

    一.实验拓扑图 二.实验编址 三.实验步骤 1.给对应的PC设置对应的IP和掩码还有接口,以及根据需要划分不同的vlan区域,再用文本标记出不同部门. 2. 启动设备(全选) 3.创建vlan并配置a ...

  10. Git(12)-- Git 分支 - 分支简介

    @ 目录 1.分支简介 1.1.初始化并首次提交 首次提交对象及其树结构: git 的 cat-file 的命令用法: 1.2.修改并第二次提交 第二次提交对象及其树结构: 1.3.修改并第三次提交 ...