NOIP模拟50

2021年9月11日 9点热度 0条评论 来源: Varuxn

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

前言

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

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;
}
    原文作者:Varuxn
    原文地址: https://www.cnblogs.com/Varuxn/p/15253103.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。