noip模拟15[夜莺与玫瑰·影子·玫瑰花精]

noip模拟15 solution

只有60分,还是苦想死想2h想出来的,就是第一题,然后后面就没有时间咧

所以整场只有\(60pts\),还是有暴力分没有拿到的

让我最后悔的事就是给\(T3\)留的时间太少了,导致线段树没有编译就交上去了\(compile\;error\)

不过说实话这个出题人是真的有才,小题目背景写的非常的不错,导致我看题目看了好久

·

T1 夜莺与玫瑰

这题面是真的有意境,但是这题是真难,真难,真难,真难

我还是把\(O(Tn^2)\)的算法给搞出来了,我的式子比较ex,比较难理解,我还是直接写题解吧

我们可以发现,所有对答案造成贡献的\(dead\;line\)的斜率一定不同,

就可以直接根据这个性质,只用(1,1)去跟其他的点连边,我们要求(i-1)与(j-1)互质

(这里为了简化计算,用(0,0)来和其他连边,直接判断(i,j)是否互质)

所以我们直接\(O(n^2)\)枚举\(O(logn)\)判断是否互质,得到答案

这样我们找到了所有的斜率,然鹅相同的斜率还会有平行的直线,所以我们对这些分别乘上系数,就是\((n-i)(n-j)\)

当然我们一定会算重一些线,那些头尾相接的线,这个系数是需要减去的

因为每两个线就会合成一条线,这个可以通过小矩形合并成大矩形来解释,系数是\((n-2*i)(m-2*i)\)

我们最终得到的式子就是:

\(ans=\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1]((n-i)(m-j)-max(0,n-i*2)max(0,m-j*2)))\)

中括号就是0,1;

所以题解上直接告诉我对这个玩意取一个前缀和,没给我气死,啥玩意不说明白点,气人!!!!

经过我的一番思考之后,我把这个前缀和搞了出来,复杂度\(O(n^2logn)\)

继续观察式子,只观察前一半,先不管后面的max,自己推一推就很容易发现,前面的加和就是gcd的前缀和的前缀和,两遍!!

因为每一个数都加了它该加的次数就是一个前缀和的前缀和,这个是\(n^2logn\)的,询问是\(O(1)\)

这个怎么说??对后面的max进行化简

\(\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1](n-i*2)(m-j*2)\)

\(\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1]2(\dfrac{n}{2}-i)2(\dfrac{m}{2}-j)\)

\(\sum\limits^{n-1}_{i=1}\sum\limits^{m-1}_{j=1}[gcd(i,j)==1]4(\dfrac{n}{2}-i)(\dfrac{m}{2}-j)\)

你就突然发现这后面其实就是一个\(f[\dfrac{n}{2}][\dfrac{m}{2}]\)

但是呢,你后面还要处理一些边界问题,奇数的时候,中间会有剩余的一行,这样会漏条件,所以我们要把整个图完整的分成四块

这里具体看代码

AC_code


#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=4005;
const int mod=1<<30;
int T,n,m;
int c[N][N],f[N][N];
ll ans;
int GCD(int x,int y){
	return y==0?x:GCD(y,x%y);
}
signed main(){
	scanf("%d",&T);
	n=4001;m=4001;
	for(re i=2;i<=n;i++){
		for(re j=2,now;j<=m;j++){
			now=0;
			if(GCD(i-1,j-1)==1){
				now=1;
			}
			c[i][j]=(1ll*now+c[i-1][j]+c[i][j-1]+mod-c[i-1][j-1])%mod;
		}
	}
	for(re i=2;i<=n;i++){
		for(re j=2;j<=m;j++){
			f[i][j]=(1ll*f[i-1][j]+f[i][j-1]+mod-f[i-1][j-1]+c[i][j])%mod;
		}
	}
	//cout<<c[1][2]<<endl;
	//cout<<"sb"<<endl;
	for(re i=1;i<=T;i++){
		scanf("%d%d",&n,&m);
		int nl=n>>1,nr=n+1>>1;
		int ml=m>>1,mr=m+1>>1;
		//cout<<nl<<" "<<nr<<" "<<ml<<" "<<mr<<endl;
		ans=(1ll*f[n][m]+1ll*mod*4-f[nl][ml]-f[nl][mr]-f[nr][ml]-f[nr][mr])%mod;
		//cout<<ans<<endl;
		ans=(ans*2)%mod;
		ans=(ans+n+m)%mod;
		printf("%lld\n",ans);
	}
}

这个是\(O(n^2logn)\)的复杂度,然而在JYXHYX\(O(n^2)\)\(GCD\)的优化下成功达到\(O(n^2)\),但是好像还有人\(O(n)\)....

·

T2 影子

叫你寻找\(min*sum\)的最大值,所以我直接\(O(n^2logn)\)暴力循环加LCA走了

在考场上的时候,脑子里是有一瞬间想到点分治的,然后瞬间想到忘干净了

考场下来,用点分治实现了一下,每次找重心都以权值最小的点为中心,然后只拿到了55pts

点分治_55pts
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int T,n;
ll vmn[N],ans;
int to[N*2],nxt[N*2],head[N],rp;
ll val[N*2];
void add_edg(int x,int y,ll z){
	to[++rp]=y;
	val[rp]=z;
	nxt[rp]=head[x];
	head[x]=rp;
}
ll ms[N],su,mx,rt;
bool vis[N];
void get_rt(int x,int f){
	ms[x]=0x3f3f3f3f;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f||vis[y])continue;
		get_rt(y,x);
	}
	if(vmn[x]<mx)mx=vmn[x],rt=x;
}
int cnt;
struct node{
	ll len,bl;
}rot[N];
void get_dis(int x,int f,ll d,int bl){
	rot[++cnt].len=d;
	rot[cnt].bl=bl;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f||vis[y])continue;
		get_dis(y,x,d+val[i],bl);
	}
}
void get_Dis(int x){
	cnt=0;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		//cout<<y<<" ";
		if(vis[y])continue;
		get_dis(y,x,val[i],y);
	}
}
ll maxn,maxx,id;
ll get_ans(int x){
	get_Dis(x);
	maxn=0;maxx=0;id=0;
	for(re i=1;i<=cnt;i++){
		//cout<<rot[i].len<<" "<<rot[i].bl<<endl;
		if(rot[i].len>maxn&&rot[i].bl!=id){
			maxx=maxn;maxn=rot[i].len;
			id=rot[i].bl;
		}
		else if(rot[i].len>maxn)maxn=rot[i].len;
		else if(rot[i].len>maxx&&rot[i].bl!=id)
			maxx=rot[i].len;
	}
	//cout<<maxn<<" "<<maxx<<endl;
	return maxn+maxx;
}
void sol(int x){
	vis[x]=true;
	ans=max(ans,1ll*get_ans(x)*vmn[x]);
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(vis[y])continue;
		mx=0x3f3f3f3f;
		get_rt(y,x);
		sol(rt);
	}
}
void clear(){
	rp=0;memset(head,0,sizeof(head));
	memset(vis,false,sizeof(vis));
}
signed main(){
	scanf("%d",&T);
	while(T--){
		clear();
		scanf("%d",&n);
		for(re i=1;i<=n;i++)scanf("%lld",&vmn[i]);
		for(re i=1,x,y;i<n;i++){
			ll z;scanf("%d%d%lld",&x,&y,&z);
			add_edg(x,y,z);add_edg(y,x,z);
		}
		mx=0x3f3f3f3f;
		get_rt(1,0);
		//cout<<"rt="<<rt<<endl;
		ans=0;sol(rt);
		printf("%lld\n",ans);
	}
}

我也不知道是我的思路错了还是点分治真的不能过。。。

·

所以我开开心心的去打并查集了,这个非常好想

先把所有的点按照权值从大到小排序,按照顺序插入到并查集里,然后每次在并查集中寻找最长链。

这里有一个结论,将两个联通块合并时,合并后的最长链的端点就是原来四个端点的其中两个

枚举六种情况,即使是原来的两条链最长,之前的权值比现在的大,所以不会对答案造成任何影响

这里可以直接使用树链剖分或者倍增直接求lca求距离

并查集_AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int T,n;
ll ans;
struct POT{
	ll rmn,id;
	bool operator < (POT x)const{
		return x.rmn>rmn;
	}
}sca[N];
priority_queue<POT> q;
int to[N*2],nxt[N*2],head[N],rp;
ll val[N*2];
void add_edg(int x,int y,ll z){
	to[++rp]=y;
	val[rp]=z;
	nxt[rp]=head[x];
	head[x]=rp;
}
int dfn[N],cnt,fa[N];
int siz[N],son[N],top[N];
ll dep[N];
void dfs1(int x,int f){
	siz[x]=1;son[x]=0;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		fa[y]=x;dep[y]=dep[x]+val[i];
		dfs1(y,x);
		siz[x]+=siz[y];
		if(!son[x]||siz[y]>siz[son[x]])son[x]=y;
	}
}
void dfs2(int x,int f){
	top[x]=f;dfn[x]=++cnt;
	if(son[x])dfs2(son[x],f);
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==son[x]||y==fa[x])continue;
		dfs2(y,y);
	}
}
int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}
int fai[N],d[N][2];
ll DIS(int x,int y){
	return dep[x]+dep[y]-2*dep[LCA(x,y)];
}
ll ds[N];
int find(int x){
	return x==fai[x]?x:fai[x]=find(fai[x]);
}
bool vis[N];
void clear(){
	rp=0;memset(head,0,sizeof(head));
	cnt=0;dep[1]=1;ans=0;
	memset(vis,false,sizeof(vis));
	for(re i=1;i<=n;i++){
		fai[i]=i;
		d[i][0]=d[i][1]=i;
		ds[i]=0;
	}
}
signed main(){
	//freopen("b.ans","w",stdout);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		clear();
		for(re i=1;i<=n;i++){
			scanf("%lld",&sca[i].rmn);sca[i].id=i;
			q.push(sca[i]);
		}
		for(re i=1,x,y;i<n;i++){
			ll z;scanf("%d%d%lld",&x,&y,&z);
			add_edg(x,y,z);add_edg(y,x,z);
		}
		dfs1(1,0);dfs2(1,1);
		while(!q.empty()){
			int now=q.top().id;q.pop();
			for(re i=head[now];i;i=nxt[i]){
				int y=to[i];
				if(sca[y].rmn<sca[now].rmn)continue;
				int fn=find(now),fy=find(y);
				int ld,rd;
				ll len=0,getl;
				if(len<ds[fn]){len=ds[fn];ld=d[fn][0];rd=d[fn][1];}
				if(len<ds[fy]){len=ds[fy];ld=d[fy][0];rd=d[fy][1];}
				if(len<(getl=DIS(d[fn][0],d[fy][0]))){len=getl;ld=d[fn][0];rd=d[fy][0];}
				if(len<(getl=DIS(d[fn][0],d[fy][1]))){len=getl;ld=d[fn][0];rd=d[fy][1];}
				if(len<(getl=DIS(d[fn][1],d[fy][0]))){len=getl;ld=d[fn][1];rd=d[fy][0];}
				if(len<(getl=DIS(d[fn][1],d[fy][1]))){len=getl;ld=d[fn][1];rd=d[fy][1];}
				fai[fn]=fy;ds[fy]=len;d[fy][0]=ld;d[fy][1]=rd;
				int f=find(now);
				ans=max(ans,ds[f]*sca[now].rmn);
			}
			
		}
		printf("%lld\n",ans);
	}
}

·

T3 玫瑰花精

所以这个题有没有让你想到hotel这道题???

可以说是完全一模一样的,

在这个题中,注意左边最优,注意取等问题

注意边界问题,左右边界不需要除以2

然后就维护三个变量组:

1.mmx:最大区间的长度  lmx:最大区间的左端点   rmx:最大区间的右端点
2.mlx:左端点为l的区间最大长度   rlx:左区间的右端点
3.mrx:右端点为r的区间最大长度   lrx:右区间的左端点

直接上代码

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=2e5+5;
int n,m;
int bl[1000005];
struct XDS{
	#define ls x<<1
	#define rs x<<1|1
	int mmx[N*8],mlx[N*8],mrx[N*8];
	int lmx[N*8],rmx[N*8],rlx[N*8],lrx[N*8];
	int rex,rel,rer;
	void pushup(int x,int l,int r){
		int mid=l+r>>1;
		
		mmx[x]=mmx[ls];lmx[x]=lmx[ls];rmx[x]=rmx[ls];
		if(((mmx[x]+1)>>1)<((mlx[rs]+mrx[ls]+1)>>1))
			{mmx[x]=mlx[rs]+mrx[ls];lmx[x]=lrx[ls];rmx[x]=rlx[rs];}
		if(((mmx[x]+1)>>1)<((mmx[rs]+1)>>1))
			{mmx[x]=mmx[rs];lmx[x]=lmx[rs];rmx[x]=rmx[rs];}
		
		mlx[x]=mlx[ls];rlx[x]=rlx[ls];
		if(mlx[x]==mid+1-l){mlx[x]+=mlx[rs];rlx[x]=rlx[rs];}

		mrx[x]=mrx[rs];lrx[x]=lrx[rs];
		if(mrx[x]==r-mid){mrx[x]+=mrx[ls];lrx[x]=lrx[ls];}

		return ;
	}
	void build(int x,int l,int r){
		mlx[x]=mrx[x]=mmx[x]=r-l+1;
		lmx[x]=lrx[x]=l;
		rlx[x]=rmx[x]=r;
		if(l==r)return ;
		int mid=l+r>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		return;
	}
	void ins(int x,int l,int r,int pos,int v){
		if(l==r){
			if(v==2){
				mmx[x]=1;mlx[x]=1;mrx[x]=1;
				lmx[x]=pos;rmx[x]=pos;lrx[x]=pos;rlx[x]=pos;
			}
			else{
				mmx[x]=0;mlx[x]=0;mrx[x]=0;
				lmx[x]=pos+1;rmx[x]=pos-1;lrx[x]=pos+1;rlx[x]=pos-1;
			}
			return ;
		}
		int mid=l+r>>1;
		if(pos<=mid)ins(ls,l,mid,pos,v);
		else ins(rs,mid+1,r,pos,v);
		pushup(x,l,r);
		return ;
	}
	#undef ls
	#undef rs
}xds;
bool vis[N];
signed main(){
	scanf("%d%d",&n,&m);
	xds.build(1,1,n);
	for(re i=1,typ,x;i<=m;i++){
		scanf("%d%d",&typ,&x);
		if(typ==2){
			xds.ins(1,1,n,bl[x],2);
			vis[bl[x]]=0;
		}
		else{
			int p=(xds.rmx[1]+xds.lmx[1])>>1;
			if(xds.lmx[1]==1||xds.mlx[1]>=(xds.mmx[1]+1)/2)p=1;
			else if(xds.rmx[1]==n||xds.mrx[1]>(xds.mmx[1]+1)/2)p=n;
			printf("%d\n",p);
			xds.ins(1,1,n,p,1);
			vis[p]=1;
			bl[x]=p;
		}
	}
}

就这几个小边界问题,我对着数据调了一晚上。。。。。

posted @ 2021-07-16 10:43  fengwu2005  阅读(68)  评论(0编辑  收藏  举报