7.14考试总结(NOIP模拟15)[夜莺与玫瑰·影子·玫瑰花精]

梦总是有会醒来的时候,不会醒的梦总有一天会变成悲伤。

前言

这次考试的思维含量有一点大(此时距离考试还有 7min 而我的总结还没写完。。)

但是对于以前的考试来讲还是有所进步的,毕竟在考试的时候还是第一次尝试着自己推柿子,最后也是成功的推出部分分的柿子。。

唯一遗憾的一点就是在 wzr 的领导下,我在 256MB 的内存下开了 \(10^4\times 10^4\) 的 long long 数组。

然后,就 MLE 0pts 了,我的 35 分就。。。(现在距离所谓的考试还有 2min ,但是考试突然没了)

再然后又说 14:40 开始考。。。起起伏伏。。。

对于第三题的话,裸的暴力,处理之后直接 sort 就可以搞到 60pts ,但是我一想到 sort 的复杂度就直接开了一个数组,手动一个一个按顺序插入,最后弄巧成拙,喜提 0pts。

虽然现在正在考试中,但这并不影响我写之前的考试总结,毕竟仅剩下 7min 了,我对于别的题也没有什么好的想法。

T1 夜莺与玫瑰

解题思路

对于 60pts 来说,显然的是要一个对于每一次询问 \(\mathcal{{O}}(n^2)\) 查询的办法。

于是我苦死冥想了整整 100min 想到了这个部分分。

(第一次隔天写文章。。),对于每一种线的最短线段一定会满足 \(\gcd(i-1,j-1)=1\)

在下面计算以及说明的时候为了方便,我们把所有的初始变量都减去一个 1 ,所以关于下面的说明可能会口胡多或者少一个 1 读者请意会一下(逃。

然后在 \((n,m)\) 的区间内,对于最短的线段(算上重复的)一共会有 \((n-i)\times (m-j)\) 条。

但是通过画图就可以发现 重复的就会有 \((n-2\times i)\times (m-2\times j)\) 条。

也就是下面的柿子:

\[\sum\limits_{i=1}^{n-1}\sum\limits_{j=1}^{m-1}[\gcd(i,j)=1]((n-i)\times(m-j)-\max(n-2\times i,0)\times \max(m-2\times j,0)) \]

对于这个柿子维护二维前缀和。

第一个前缀和计算范围内的 \(\gcd\) 的数量,可以理解为从某一点出发的最短线段的个数。

然后在此基础上维护一个二维前缀和 \(pre_{n,m}=[\gcd(i,j)=1]((n-i)\times(m-j)\)

然后我们尝试把这个柿子运用到之前的柿子上发现, 其实:

\[(n-2\times i)\times (m-2\times j)=4\times(\dfrac{n}{2}-i)\times(\dfrac{m}{2}-j)=4\times pre_{\frac{n}{2},\frac{m}{2}} \]

接下来就可以直接套用公式并且对于奇数的情况进行处理就好了。

code

#include<bits/stdc++.h>
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=1e4+10,M=4e3+10,mod=1073741824;
int T,n,m,f[M][M],vis[M][M],pre[M][M];
struct Node
{
	int x,y;
}q[N];
int Gcd(int x,int y)
{
	if(!y)	return x;
	return Gcd(y,x%y);
}
void Init()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			vis[i][j]=(Gcd(i,j)==1);
			vis[i][j]+=vis[i][j-1]+vis[i-1][j]-vis[i-1][j-1];
			pre[i][j]=vis[i][j]+pre[i][j-1]+pre[i-1][j]-pre[i-1][j-1];
		}
}
int solve(int x,int y)
{
	int ans=0,mix=x/2,miy=y/2;
	ans=pre[x-1][y-1];
	ans=(ans+(4ll*mod-4*pre[mix-1][miy-1]))%mod;
	if(x&1)	for(int i=1;i<=miy;i++)	ans=(ans+1ll*(2ll*mod-2ll*vis[mix][i-1]))%mod;
	if(y&1)	for(int i=1;i<=mix;i++)	ans=(ans+1ll*(2ll*mod-2ll*vis[i-1][miy]))%mod;
	if(x&1 && y&1)	ans=(ans+1ll*(mod-vis[mix][miy]))%mod;
	return (ans*2+x+y)%mod;
}
signed main()
{
	T=read();
	for(int i=1;i<=T;i++)
	{
		q[i].x=read();
		q[i].y=read();
		n=max(n,q[i].x);
		m=max(m,q[i].y);
	}
	Init();
	for(int i=1;i<=T;i++)
		printf("%d\n",solve(q[i].x,q[i].y));
	return 0;
}

T2 影子

解题思路

对于暴力算法可以分别以每个节点为根,分别查询这个节点到其他节点的距离以及最小值。

时间复杂度 \(\mathcal{O}(n^2)\) 空间复杂度 \(\mathcal{O}(n)\)

然后在考场上我搞了一个 \(n^2\) 的复杂度,再然后就 MLE 0pts,痛失 35pts(\(code\)) 。

正解是首先对于所有的节点按节点值从大到小进行排序(这样在搞的时候就不用考虑节点权值的关系了),然后把当前集合中的最长路的长度和两个端点用冰茶几维护。

用冰茶几合并这个节点以及与他有连边的节点所在的联通块。

维护的时候主要分为两大种情况:

  1. 原来的两个联通块分别的长度

  2. 由两个最长路的两个端点分别连接而成,共有 4 种。

每次冰茶几维护直接用两种值的乘积,更新既可,代码实现不算太难。。

code

#include<bits/stdc++.h>
#define int long long
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=1e5+10,M=2e4,INF=1e9;
int n,m,T,ans,tim,fat[N],dep[N],son[N],siz[N],dfn[N],fa[N],topp[N],dis[N],clo[N];
int tot,head[N<<1],nxt[N<<1],ver[N<<1],edge[N<<1];
struct Node
{
	int id,val;
}s[N];
struct Road
{
	int l,r,len;
	void clear()
	{
		l=r=len=0;
	}
}pat[N],dist[10];
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)
{
	pat[x].l=pat[x].r=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;
		fat[to]=x;
		dfs1(to);
		siz[x]+=siz[to];
		if(siz[to]>siz[son[x]])
			son[x]=to;
	}
}
void dfs2(int x,int tp)
{
	topp[x]=tp;
	dfn[x]=++tim;
	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)
{
	if(!x||!y)	return 0;
	if(x==y)	return x;
	while(topp[x]^topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])
			swap(x,y);
		x=fat[topp[x]];
	}
	if(dep[x]>dep[y])
		swap(x,y);
	return x;
}
bool comp(Node x,Node y)
{
	return x.val>y.val;
}
int find(int x)
{
	if(fa[x]==x)	return fa[x];
	return fa[x]=find(fa[x]);
}
int Dist(int x,int y)
{
	return dis[x]+dis[y]-2*dis[LCA(x,y)];
}
bool cmp(Road x,Road y)
{
	return x.len>y.len;
}
void merge(int x,int y)
{
	int minn=clo[x];
	x=fa[find(x)];
	y=fa[find(y)];
	dist[1]=pat[x];
	dist[2]=pat[y];
	dist[3]=(Road){pat[x].l,pat[y].l,Dist(pat[x].l,pat[y].l)};
	dist[4]=(Road){pat[x].l,pat[y].r,Dist(pat[x].l,pat[y].r)};
	dist[5]=(Road){pat[x].r,pat[y].l,Dist(pat[x].r,pat[y].l)};
	dist[6]=(Road){pat[x].r,pat[y].r,Dist(pat[x].r,pat[y].r)};
	sort(dist+1,dist+7,cmp);
	pat[x]=dist[1];
	fa[y]=x;
	ans=max(ans,dist[1].len*minn);
}
void solve(int x)
{
	for(int i=head[x];i;i=nxt[i])
		if(clo[ver[i]]>=clo[x])
			merge(x,ver[i]);
}
void dfs3(int x,int fro)
{
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fro)	continue ;
		dfs3(to,x);
	}
	dis[x]=dep[x]=dfn[x]=siz[x]=son[x]=topp[x]=head[x]=fa[x]=fat[x]=clo[x]=0;
	pat[x].clear();
}
void work()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		clo[i]=read();
		s[i]=(Node){i,clo[i]};
	}
	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);
	}
	for(int i=1;i<=n;i++)
		fa[i]=i;
	sort(s+1,s+n+1,comp);
	dfs1(1);
	dfs2(1,1);
	for(int i=1;i<=n;i++)
		solve(s[i].id);
	printf("%lld\n",ans);
	dfs3(1,0);
	tot=tim=ans=0;
}
signed main()
{
	T=read();
	while(T--)	work();
	return 0;
}

T3 玫瑰花精

解题思路

有一点像之前做过的 旅馆,但是不完全一样。

用线段树维护序列,主要维护 7 个值:lmx,ll,rr,rmx,llen,rlen,len

主要分为三个区间:

  1. 与整个区间的左边界相连的最长长度(llen)以及端点(ll)

  2. 同样的与整个区间的右边界相连的最长长度(rlen)以及端点(rr)

  3. 整个区间内的除了左右两个区间的所有未入住的区间中最长的长度(len)以及左右端点(lmx,rmx)

然后在维护时候主要对于上面的三个区间进行查找。

然后就是单点修改,区间查询,在搞的时候比较考验码力,具体实现细节见代码:

code

#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
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,M=1e6+10;
int n,m,s[M];
struct Segment_Tree
{
	int lmx,ll,rr,rmx,llen,rlen,len;
}tre[N<<2];
inline void push_up(int x,int l,int r)
{
	int mid=(l+r)>>1;
	if( max( ((tre[rs].len+1)>>1),((tre[rs].llen+tre[ls].rlen+1)>>1) ) <= ((tre[ls].len+1)>>1) )
	{
		tre[x].len=tre[ls].len;
		tre[x].lmx=tre[ls].lmx;
		tre[x].rmx=tre[ls].rmx;
	}
	else	if( max( ((tre[rs].len+1)>>1),((tre[ls].len+1)>>1) ) <= ((tre[rs].llen+tre[ls].rlen+1)>>1) )
	{
		tre[x].len=tre[rs].llen+tre[ls].rlen;
		tre[x].lmx=tre[ls].rr;
		tre[x].rmx=tre[rs].ll;
	}
	else	if( max( ((tre[rs].llen+tre[ls].rlen+1)>>1),((tre[ls].len+1)>>1) ) <= ((tre[rs].len+1)>>1) )
	{
		tre[x].len=tre[rs].len;
		tre[x].lmx=tre[rs].lmx;
		tre[x].rmx=tre[rs].rmx;
	}
	tre[x].ll=tre[ls].ll;
	tre[x].rr=tre[rs].rr;
	tre[x].llen=tre[ls].llen;
	tre[x].rlen=tre[rs].rlen;
	if(tre[x].llen==mid-l+1)
	{
		tre[x].llen=mid-l+1+tre[rs].llen;
		tre[x].ll=tre[rs].ll;
	}
	if(tre[x].rlen==r-mid)
	{
		tre[x].rlen=r-mid+tre[ls].rlen;
		tre[x].rr=tre[ls].rr;
	}
}
inline void build(int x,int l,int r)
{
	tre[x].len=tre[x].llen=tre[x].rlen=r-l+1;//将可以入住的区间搞到最大
	tre[x].lmx=tre[x].rr=l;//同时更新左右端点
	tre[x].ll=tre[x].rmx=r;
	if(l==r)	return ;
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
}
inline void insert(int x,int l,int r,int pos)
{
	if(l==r)
	{
		tre[x].len=tre[x].llen=tre[x].rlen=0;//标记入住
		tre[x].lmx=tre[x].rr=pos+1;//三个区间错开
		tre[x].ll=tre[x].rmx=pos-1;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)	insert(ls,l,mid,pos);//向下递归
	else	insert(rs,mid+1,r,pos);
	push_up(x,l,r);
}
inline void delate(int x,int l,int r,int pos)
{
	if(l==r)
	{
		tre[x].len=tre[x].llen=tre[x].rlen=1;
		tre[x].lmx=tre[x].rr=tre[x].ll=tre[x].rmx=pos;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)	delate(ls,l,mid,pos);//递归更新
	else	delate(rs,mid+1,r,pos);
	push_up(x,l,r);
}
inline void solve1(int pos)
{
	int ans=(tre[1].lmx+tre[1].rmx)>>1;
	if(tre[1].llen>=(tre[1].len+1)/2)	ans=1;//只剩下1节点或者左侧的长度大于中间最大的一半
	else	if(tre[1].rlen>(tre[1].len+1)/2)	ans=n;//只剩下n节点或者右侧更优
	s[pos]=ans;
	insert(1,1,n,ans);//记录并更新
	printf("%d\n",ans);
}
inline void solve2(int pos)
{
	delate(1,1,n,s[pos]);
}
signed main()
{
	n=read();
	m=read();
	build(1,1,n);
	while(m--)
	{
		int opt,pos;
		opt=read();
		pos=read();
		if(opt==1)	solve1(pos);
		else	solve2(pos);
	}
	return 0;
}
posted @ 2021-07-16 11:30  Varuxn  阅读(86)  评论(0编辑  收藏  举报