7.28考试总结(NOIP模拟26)[神炎皇·降雷皇·幻魔皇]

或许只需一滴露水,便能守护这绽放的花朵。

前言

疯狂挂分,本来T2是想用树状数组优化一下的不知道为啥后来看了一下就少看了一层循环,

然后就想,我都 n 的复杂度了,足以搞过第一问了,还优化啥呀。。。。

在然后就挂了 20pts。。。。

后来一看 T3 的斐波那契数列,就上头,最后不仅正解没搞出来,就连 40pts 的暴力也没打,裂开。

对这次考试感觉比较特别的就是每个题的名字了。。

T1 神炎皇

解题思路

这题考场上是直接开始了找规律,发现从 1 到 n 的每一个有平方因子的数都会产生其最大平方因子开方后减一的贡献。

假设最大平方因子为 x ,贡献就是 \(\sqrt{x}-1\)

然后对于所有的都可以 n 的复杂度推出来,这样就有了 60pts (其实可以优化到 80pts)

枚举 \(\gcd(a,b)=d\) ,设\(a'=\dfrac{a}{d}\),\(b'=\dfrac{b}{d}\)

然后就有了\((a'+b')d\;|\;a'\times b'\times d^2\)

因为\(\gcd(a',b')=1\)所以\(a'+b'\)一定是 d 的因数。

接下来就可以枚举\((a'+b')=k\) 那么合法的 d 就有 \(\dfrac{n}{k^2}\) 种取值。

毕竟\((a‘+b')\times d\le n\) 并且 \((a'+b')|d\)

合法的 (a',b') 有\(\phi(k)\)对,因为,\(a'+b'=k\)嘛。。。

直接线性筛搞出来之后直接算就行

code

60pts

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<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=1e7+10,M=1e4+10;
int n,top,ans,sta[M];
int vis[N];
void init()
{
	for(int i=2;i*i<=n;i++)
		sta[++top]=i,vis[i*i]=i;
	for(int i=2;i<=n;i++)
	{
		if(vis[i])	continue;
		for(int j=1;j<=top&&sta[j]*sta[j]*i<=n;j++)
			vis[sta[j]*sta[j]*i]=sta[j];
		if(i*4>n)	break;
	}
}
signed main()
{
	n=read();
	init();
	for(int i=1;i<=n;i++)
		if(vis[i])
			ans+=vis[i]-1;
	printf("%lld",ans);
	return 0;
}

正解

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<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=1e7+10;
int n,m,ans,cnt,f[N],phi[N],pri[N];
bool vis[N];
void init()
{
	phi[1]=1;
	for(int i=2;i<=m;i++)
	{
		if(!vis[i])
		{
			pri[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&pri[j]*i<=m;j++)
		{
			vis[i*pri[j]]=true;
			if(i%pri[j]==0)
			{
				phi[i*pri[j]]=phi[i]*pri[j];
				break;
			}
			phi[i*pri[j]]=phi[i]*(pri[j]-1);
		}
	}
}
signed main()
{
	n=read();
	m=sqrt(n);
	init();
	for(int i=2;i<=m;i++)
		ans+=phi[i]*(n/(i*i));
	printf("%lld",ans);	
	return 0;
}

T2 降雷皇

解题思路

就一大水题。。。

第一问比较好求,直接树状数组优化就可以了,打法大概和树状数组求逆序对有些相似。

第二问对于暴力的柿子进行线段树优化,每次求解的时候传过来两个值。

一个是区间的最大值,以及对应的方案数。

继承的时候如果两个子区间的最大值相同,方案数就是加和,否则就是较大值的方案数。

实现上有一些细节问题,但也不是特别难调,算是比较水的一个题了。

code

40pts裸的暴力 DP

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<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=1e5+10,mod=123456789;
int n,task,ans,answer,s[N],f[N],siz[N];
signed main()
{
	n=read();
	task=read();
	for(int i=1;i<=n;i++)
		s[i]=read();
	fill(f+1,f+n+1,1);
	siz[0]=1;
	for(int i=2;i<=n;i++)
		for(int j=1;j<i;j++)
			if(s[j]<s[i])
				f[i]=max(f[i],f[j]+1);
	for(int i=1;i<=n;i++)
		ans=max(f[i],ans);
	printf("%lld",ans);
	if(!task)	return 0;
	for(int i=1;i<=n;i++)
		for(int j=0;j<i;j++)
			if(f[j]+1==f[i]&&s[i]>s[j])
				siz[i]=(siz[i]+siz[j])%mod;
	for(int i=1;i<=n;i++)
		if(f[i]==ans)
			answer=(answer+siz[i])%mod;
	printf("\n%lld",answer);
	return 0;
}

正解

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define ls t[x].l
#define rs t[x].r
#define f() cout<<"Pass"<<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=1e5+10,mod=123456789;
int n,cnt,task,all,ans,answer,root,lsh[N],s[N],f[N],siz[N];
int tre[N];
struct VOP_Segment_Tree
{
	int l,r,dat,val;
}t[N*80];
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int num)
{
	for(int i=x;i<=cnt;i+=lowbit(i))
		tre[i]=max(tre[i],num);
}
int ask(int x)
{
	int temp=0;
	for(int i=x;i;i-=lowbit(i))
		temp=max(temp,tre[i]);
	return temp;
}
void push_up(int x)
{
	t[x].val=max(t[ls].val,t[rs].val);
	if(t[ls].val==t[rs].val)
		t[x].dat=t[ls].dat+t[rs].dat;
	else	t[x].dat=(t[ls].val>t[rs].val)?t[ls].dat:t[rs].dat;
}
void insert(int &x,int l,int r,int pos,int val1,int val2)
{
	if(!x)	x=++all;
	if(l==r)
	{
		if(t[x].val==val1)	t[x].dat+=val2;
		else	t[x].dat=(t[x].val>val1)?t[x].dat:val2;
		t[x].val=max(t[x].val,val1);
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)	insert(ls,l,mid,pos,val1,val2);
	else	insert(rs,mid+1,r,pos,val1,val2);
	push_up(x);
}
pair<int,int> query(int x,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)	return make_pair(t[x].val,t[x].dat);
	int mid=(l+r)>>1;
	pair<int,int> tmp1=query(ls,l,mid,L,R),tmp2,temp;
	tmp1.second%=mod;
	if(R>mid)	tmp2=query(rs,mid+1,r,L,R);
	else	return tmp1;
	temp.first=max(tmp1.first,tmp2.first);
	if(tmp1.first==tmp2.first)
		temp.second=tmp1.second+tmp2.second;
	else	temp.second=(tmp1.first>tmp2.first)?tmp1.second:tmp2.second;
	temp.second%=mod;
	return temp;
}
signed main()
{
	n=read();
	task=read();
	for(int i=1;i<=n;i++)
		lsh[i]=s[i]=read();
	sort(lsh+1,lsh+n+1);
	cnt=unique(lsh+1,lsh+n+1)-lsh-1;
	for(int i=1;i<=n;i++)
		s[i]=lower_bound(lsh+1,lsh+cnt+1,s[i])-lsh;
	fill(f+1,f+n+1,1);
	siz[0]=1;
	for(int i=1;i<=n;i++)
	{
		f[i]=max(f[i],ask(s[i])+1);
		add(s[i]+1,f[i]);
	}
	for(int i=1;i<=n;i++)
		ans=max(f[i],ans);
	printf("%lld",ans);
	if(!task)	return 0;
	cnt++;
	insert(root,1,cnt,s[1]+1,1,1);
	for(int i=2;i<=n;i++)
	{
		pair<int,int>	temp=query(root,1,cnt,1,s[i]);
		siz[i]=max(1ll,temp.second%mod);
		insert(root,1,cnt,s[i]+1,f[i],siz[i]);
	}
	for(int i=1;i<=n;i++)
		if(f[i]==ans)
			answer=(answer+siz[i])%mod;
	printf("\n%lld",answer);
	return 0;
}

T3 幻魔皇

解题思路

不难发现所有的都是和斐波那契有关的(比如每一层的黑色或者白色节点的数量)。

先抛开最顶部的白色节点,考虑下面所有节点的贡献,最后单独处理就好了。

考虑分别计算以黑色节点和以白色节点为根的子树的贡献。

对于白色节点为根节点的比较简单,直接循环子树里每一层的就好了。

黑色节点子树中的白色节点分为两种情况:

  1. 直接与黑色子树根节点相连的。

  2. 其它的

对于第二种情况直接斐波那契数列推就好了,分别枚举左子树的深度和右子树的深度就好了。

第一种情况也类似于上面,只枚举另一侧子树的深度计算就好了。

这样就是 80pts 做法,100pts 只要把前两维初始化一下就好了。

code

80pts

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<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=1e4+10,mod=123456789;
int n,f[N],ans[N];
signed main()
{
	n=read();
	f[1]=f[2]=1;
	for(int i=3;i<=n;i++)
		f[i]=(f[i-1]+f[i-2])%mod;
	for(int i=2;i<=n;i++)
	{
		for(int j=i+2;j<=n;j++)
		{
			for(int k=i+3;k<=n;k++)
				ans[j-i+k-i]=(ans[j-i+k-i]+f[i-1]*f[j-i-1]%mod*f[k-i-2]%mod)%mod;
			ans[j-i+1]=(ans[j-i+1]+f[i-1]*f[j-i-1]%mod)%mod;
		}
		for(int j=i+1;j<n;j++)
			ans[j-i+1]=(ans[j-i+1]+f[i-2]*f[j-i]%mod)%mod;
	}
	for(int i=3;i<=n;i++)
		ans[i-1]=(ans[i-1]+f[i-2])%mod;
	for(int i=1;i<=2*n;i++)		
		printf("%lld ",ans[i]);
	return 0;
}

正解

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<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=1e4+10,mod=123456789;
int n,f[N],ans[N],q[N][N];
signed main()
{
	n=read();
	f[1]=f[2]=1;
	for(int i=3;i<=n;i++)
		f[i]=(f[i-1]+f[i-2])%mod;
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=2*n;j++)
			q[i][j]=(q[i][j]+q[i-1][j])%mod;
		for(int j=3;j<=n;j++)
			q[max(i,j)][i+j]=(q[max(i,j)][i+j]+f[i-1]*f[j-2]%mod)%mod;
	}
	for(int i=2;i<=n;i++)
	{
		for(int j=5;j<=2*(n-i);j++)
			ans[j]=(ans[j]+q[n-i][j]*f[i-1]%mod)%mod;
		for(int j=i+2;j<=n;j++)
			ans[j-i+1]=(ans[j-i+1]+f[i-1]*f[j-i-1]%mod)%mod;
		for(int j=i+1;j<n;j++)
			ans[j-i+1]=(ans[j-i+1]+f[i-2]*f[j-i]%mod)%mod;
	}
	for(int i=3;i<=n;i++)
		ans[i-1]=(ans[i-1]+f[i-2])%mod;
	for(int i=1;i<=2*n;i++)		
		printf("%lld ",ans[i]);
	return 0;
}
posted @ 2021-07-29 06:33  Varuxn  阅读(61)  评论(0编辑  收藏  举报