NOIP模擬50

Varuxn發表於2021-09-10

過分的神聖,往往比惡魔更加惡質。

前言

最大的一個收穫就是不要動不動就碼線段樹,一定要審清楚題目之後再碼!!

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;
}