多校A层冲刺NOIP2024模拟赛26 && G

多校A层冲刺NOIP2024模拟赛26 && G

T1 随机游走

考虑到达一个点后,我们该往他的那个儿子走,简单膜一下只有两个儿子的样例后发现条件是 $ \frac{w_i}{v_i} $ 越小越优先,其中 $ w_i $ 表示他到父亲的边权, $ v_i $ 表示他的点权,然后尝试推广,膜个稍微大点的样例发现完全是通用的,此时 $ w_i $ 表示 $ i $ 的子树里所有的点到父亲的边权总和, $ v_i $ 表示 $ i $ 子树里所有的点权和。然后排序再算贡献就行了。

T2 分发奖励

还真是没想到场上读了将近一个小时的题面都没读明白他要干什么,竟然被阅读理解 hack 了,然后这题直接没写。

大概题意是说,有一棵树和 q 次操作,每次选择两个点(可能相同)然后给他们的子树里面的点全部带上一种属性,然后进行完这些操作之后对于每个点 $ i $ 求出有多少个点满足和 $ i $ 有任何一种相同属性。

其实很简单,我们直接把操作都挂在点上后直接 $ dfs $ ,每次到了一个点,就把他挂着的那些操作加上去, $ dfs $ 完了回溯时就直接撤销贡献。但是这不是一个区间加,而是一个区间覆盖,然后这就导致撤销操作很难完成,但是我们注意到区间覆盖时分成每一个小段后,这个区间的 $ sum_k $ 必定等于 $ r-l+1 $ ,所以我们每次记 $ tag_k $ 表示他被直接覆盖了几次,那么撤销时直接 $ --tag_k $ ,如果 $ tag_k $ 不为 0 的话, $ sum_k $ 还是 $ r-l+1 $ ,否则 $ sum_k $ 在 $ k $ 本层没有贡献,所以 $ sum_k = sum_{ls(k)} + sum_{rs(k)} $ ,然后直接做就行了。

给个码
/*
GGrun
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define fi first
#define se second
#define ps push_back
#define mk make_pair
const int N=1e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;
inline ll read(){
	char c=getchar();ll x=0;bool f=0;
	while(!isdigit(c))f=c=='-'?1:0,c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
}
int n,q,a[N],b[N],fa[N],dfn[N],ed[N],cnt,sz[N];
vector<int> son[N];
inline void dfs(int x){
	dfn[x]=++cnt;sz[x]=1;
	for(auto j:son[x]){
		dfs(j);sz[x]+=sz[j];
	}
	ed[x]=cnt;
}
int tot,now,ans[N],sum[N<<2],tag[N<<2];
vector<int> v[N];
vector<int> op[N];
inline void add(int k,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R){
		if(v==0){
			if(!--tag[k]){
				if(l!=r)sum[k]=sum[k<<1]+sum[k<<1|1];
				else sum[k]=0;
			}
		}
		else ++tag[k],sum[k]=r-l+1;
		return;
	}
	int mid=l+r>>1;
	if(L<=mid)add(k<<1,l,mid,L,R,v);
	if(R>mid)add(k<<1|1,mid+1,r,L,R,v);
	if(!tag[k])sum[k]=sum[k<<1]+sum[k<<1|1];
}
inline int ask(int k,int l,int r,int pos){
	if(tag[k]||l==r)return sum[k];
	int mid=l+r>>1;
	return pos<=mid?ask(k<<1,l,mid,pos):ask(k<<1|1,mid+1,r,pos);
}
inline void sol(int x){
	vector<int> qu;
	bool zh=0;
	for(auto i:v[x]){
		if(!tot){
			++tot,add(1,1,n,dfn[x],ed[x],1),zh=1;
			// cout<<x<<' '<<i<<' '<<sum[1]<<"ADD1\n";
		}
		if(!ask(1,1,n,dfn[i])){
			add(1,1,n,dfn[i],ed[i],1),qu.ps(i);
			// cout<<x<<' '<<i<<' '<<sum[1]<<"ADD2\n";
		}
	}
	// cout<<x<<' '<<sum[1]<<endl;
	ans[x]=max(0ll,sum[1]-1);
	for(auto i:son[x])
		sol(i);
	for(auto i:qu){
		add(1,1,n,dfn[i],ed[i],0);
		// cout<<x<<' '<<i<<' '<<sum[1]<<"SHAN2\n";
	}
	if(zh){
		--tot,add(1,1,n,dfn[x],ed[x],0),zh=0;
		// cout<<x<<' '<<sum[1]<<"SHAN1\n";
	}
}
signed main(){
	freopen("reward.in","r",stdin);
	freopen("reward.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read(),q=read();
	for(int i=2;i<=n;++i)
		fa[i]=read(),son[fa[i]].ps(i);
	dfs(1);
	for(int i=1;i<=q;++i){
		a[i]=read(),b[i]=read();
		v[a[i]].ps(b[i]);v[b[i]].ps(a[i]);
	}
	sol(1);
	for(int i=1;i<=n;++i)
		cout<<ans[i]<<' ';
}

T3 卡路里

看了半天 T2 题面无果后先做了 T3。

首先肯定会 $ O(m^2 n) $ 的暴力枚举对吧,发现每次 $ O(n) $ 遍历去求贡献很唐,然后我们看看能不能优化一下,然后要想每次知道 所有奶茶的最大值无法优化,最少也得 $ O(n) $ 遍历一下,但是我们只关心他们的和,所以我们把填表改为刷表,也就是说,我们对于每个 $ a_{i,j} $ 去看他能贡献给那些区间,然后很容易想到对于每一款奶茶,拿单调栈 $ O(m) $ 的跑一下前面第一个大于等于他的,和后面第一个大于他的位置 (等于的情况放在哪边都无所谓,但必须放一边),然后设他的前驱是 $ l_j $ ,后继是 $ r_j $ ,那么 $ a_{i,j} $ 可以贡献的区间就是 左端点是 $ [ l_j , j ] $ ,右端点是 $ [ j , r_j ] $ 的区间,然后这是一个矩形加,直接上差分扫描线,树状数组维护,然后时间复杂度降为 $ O(m^2 \log(m)) $ ,标算 3e8 ,如果你算一个枚举区间时 $ \dfrac{1}{2} $ 的常数的话没准能卡过去。但是我们又发现这个树状数组也很唐,反正我们都要 $ O(m^2) $ 枚举区间了,为什么不每次直接暴力扫过去维护前缀和,然后把树状数组的 $ \log(m) $ 拿掉即可做到 $ O(m^2) $ 。

然后好像还有个 $ O(mn \log(m)) $ 的做法,大概就是直接把那个 $ d_i $ 值扔到线段树里,每次确定左端点后直接把扫描线的贡献都加进去,然后直接查最大值即可,标算比 $ O(m^2) $ 快,但实际上常数比 $ O(m^2) $ 大。

给个码
/*
GGrun
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define fi first
#define se second
#define ps push_back
#define mk make_pair
const int N=1e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;
inline ll read(){
	char c=getchar();ll x=0;bool f=0;
	while(!isdigit(c))f=c=='-'?1:0,c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f?-x:x;
}
bool f1;
int n,m,a[5050][210];
ll d[5050];
int q[10000],top,zuo[5050],you[5050];
vector<pair<pii,int> > v[5050];
ll pre[5050],jia[5050];
bool f2;
signed main(){
	freopen("calorie.in","r",stdin);
	freopen("calorie.out","w",stdout);
	// cout<<(&f1-&f2)/1024/1024<<endl;
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	n=read(),m=read();
	for(int i=1;i<m;++i)
		d[i]=read()+d[i-1];
	for(int i=1;i<=m;++i){
		for(int j=1;j<=n;++j){
			a[i][j]=read();
		}
	}
	for(int i=1;i<=n;++i){
		q[top=0]=0;
		a[0][i]=a[m+1][i]=inf;
		for(int j=1;j<=m+1;++j){
			while(top&&a[q[top]][i]<a[j][i])you[q[top--]]=j;
			zuo[j]=q[top];q[++top]=j;
		}
		for(int j=1;j<=m;++j){
			// cout<<i<<' '<<j<<' '<<zuo[j]<<' '<<you[j]<<endl;
			v[zuo[j]+1].ps({{j,you[j]-1},a[j][i]}),v[j+1].ps({{j,you[j]-1},-a[j][i]});
		}
	}
	ll ans=0;
	for(int i=1;i<=m;++i){
		for(int j=0;j<=m;++j)
			jia[j]=0;
		for(auto j:v[i]){
			jia[j.fi.fi]+=j.se;jia[j.fi.se+1]-=j.se;
		}
		ll now=0;
		for(int j=0;j<i;++j)
			now+=jia[j],pre[j]+=now;
		for(int j=i;j<=m;++j){
			now+=jia[j];
			pre[j]+=now;
			// cout<<i<<' '<<j<<' '<<pre[j]<<endl;
			ans=max(ans,pre[j]-(d[j-1]-d[i-1]));
		}
	}
	cout<<ans<<'\n';
}

T4 传话游戏

目前只会 32pts 的部分分(场上还打挂了)。

run(

G

这几天感觉挺唐的,自从 NOIP24 那场暴力没打好之后状态一直不好,赛时有很多分没拿到,而且确实感觉到旁边的人实力强大。

NOIP24:其实那场可以拿到 rk1 的分,但是 T2 40pts 的部分分没拿上,因为当时感觉切了两道题之后虽然还在打,但是少了点兴奋劲。

NOIP25:T2 n,m 写反了挂了 100pts, T4 赛时没怎么看,但其实是比较简单的线段树板子,大概T2对拍过了之后效率就极低了,当时 T2 是 10:11 交的,要是不挂还是首A呢,后面着两个小时相当于只拿了 10 pts。

梦熊那场没挂,但是会的比较少。

加赛7 其实拼满暴力也是 305pts,但是 T3挂了, T4着急打正解,正解没打出来,暴力也没打好。


然后就是这场,T2没读明白题面,T4暴力挂了 28 pts。


总的来说就是过了一些题之后紧张度就下降了,然后效率极低,基本上2个小时之后得分率就大大降低了,上次 csp-s 也是,三个小时切了三题之后半个小时没打出 T4暴力,其实并没有说半场开香槟,但是内心里已经不再那么紧张了,导致效率降低,以后会给自己多一些适当的压力,提高考试时的效率,争取调整到当时一开始集训时的状态。


反正已经走到这了,那就今天下NOIP绝命书吧,但凡这次发挥正常,省选的时候我一定能拼进省队




posted @ 2024-11-27 17:03  lzrG23  阅读(25)  评论(0编辑  收藏  举报