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

前言

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

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. Git常用命令和基础使用

    Git 参考:廖雪峰的Git教程 Git 常用命令 git config --global user.name "name" #配置git使用用户 git config --glo ...

  2. Git基本理论---重点

    Git本地有三个工作区域: 工作目录(Working Directory): 存放项目代码 暂存区(Stage/Index) : 临时存放文件的改动,保存的是文件列表信息 资源库 (Repositor ...

  3. 从零开始学习JAVA(入门基础)

    目录 博主从零开始学习JAVA(入门基础) 1.搭建JAVA开发环境 卸载JDK(未安装的请忽略) 安装JDK 2.编程语言中,何为编译型与解释型 编译型 解释型 3.第一个JAVA应用程序 4.JA ...

  4. PWN——uaf漏洞学习

    PWN--uaf漏洞 1.uaf漏洞原理 在C语言中,我们通过malloc族函数进行堆块的分配,用free()函数进行堆块的释放.在释放堆块的过程中,如果没有将释放的堆块置空,这时候,就有可能出现us ...

  5. jdk、jre环境变量配置

    1 jdk和jre的区别: (jdk:Java 开发工具包) (jre:Java 的运行环境) 只需这么记就可以了,想深入了解得自行查询相关资料 2 jdk是包含jre的,所以只需下载jdk. 官方网 ...

  6. postman之get请求

    get请求:

  7. tkinter 基础教程

    目录 介绍 模块 导入方式 API 使用 主窗口 运行窗口 组件列表介绍 Label 标签 Button 按钮 Options 属性选项 文本框 Entry 单行文本框 Text 多行文本框 文本框属 ...

  8. Create Shortcut for SSH Hosts

    You frequently visit host 10.0.7.141 for example. It's a waste to type "ssh gcp@10.0.7.141" ...

  9. CYPEESS USB3.0程序解读之---同步FIFO(slaveFifoSync)

    上一篇文章解读了CYPRESS FX3的GPIO的操作过程,下面解读同步FIFO的一个例子(slaveFifoSync). *生产者,消费者. 1.首先看DMA的回调函数(cyu3dma.h): ty ...

  10. Redmine Notes

    Mandatory authenticaion: login as Administrator, Settings -> Authentication -> Check "Aut ...