NOIP提高组模拟赛25
A. 神炎皇
有的时候,不能太相信自己的过去。。。。
这题不是很难,本来打表已经出了最终性质了,但是发现自己曾经交过这道题,于是手贱搞下来对拍一下,发现全挂了。。
输出一下发现好像原来的是错的,但是因为脑抽 ,太过相信自己,认为原来的是AC的。。
最后毅然决然交了过去的码,结果\(WA0\)。。。。。。。。。
不要耍小聪明!实践是检验真理的唯一标准!!!
通过打表可以发现规律,现在简单证明一下
枚举\(gcd(a,b)=d\)
则令\((a'+b')d|a'b'd^2\)
即\((a'+b')|a'b'd\)
因为\(gcd(a',b')=1\)
所以\(a'\perp a'+b'\) 且\(b'\perp a'+b'\)
那么\((a'+b')\perp a'b'\)
所以\((a'+b')|d\)
又因为\((a'+b')d<=n\)
所以\((a'+b')<=\sqrt n\)
枚举\(a'+b'=k\),那么合法的\(d\),有\(n/k^2\)个
互质的\(a',b'\)有\(\phi(k)\)对(\(a' \perp k\)则\(a' \perp k-a'\),令\(b'=k-a'\))
code
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
int phi[10000005],prime[10000005],cnt;
bool flag[10000005];
ll n;
int main(){
scanf("%lld",&n);
int m=sqrt(n);
for(int i=2;i<=m;++i){
if(!flag[i]){
prime[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt&&prime[j]*i<=m;++j){
flag[prime[j]*i]=1;
if(i%prime[j])phi[i*prime[j]]=phi[i]*phi[prime[j]];
else{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
}
}
ll ans=0;
for(int i=2;i<=m;++i)
ans+=1ll*phi[i]*(n/((ll)i*i));
printf("%lld\n",ans);
return 0;
}
B. 降雷皇
没啥说的,就是一个线段树优化DP
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100005;
const int mod=123456789;
int r[maxn],type,b[maxn],n;
struct node{int mx,sum;node(){mx=sum=0;}};
struct tree{
node t[maxn<<2|1];
void push_up(int x){
int ls=x<<1,rs=x<<1|1;
t[x].mx=max(t[ls].mx,t[rs].mx);
t[x].sum=0;
if(t[ls].mx==t[x].mx)t[x].sum+=t[ls].sum;
if(t[rs].mx==t[x].mx)t[x].sum+=t[rs].sum;
t[x].sum%=mod;
}
void modify(int x,int l,int r,int pos,int mx,int sum){
if(l==r){
if(mx>t[x].mx)t[x].sum=sum;
else t[x].sum=(sum+t[x].sum)%mod;
t[x].mx=mx;
return;
}
int mid=(l+r)>>1;
if(pos<=mid)modify(x<<1,l,mid,pos,mx,sum);
else modify(x<<1|1,mid+1,r,pos,mx,sum);
push_up(x);
}
node query(int x,int l,int r,int L,int R){
if(L<=l&&r<=R)return t[x];
int mid=(l+r)>>1;
node ansl,ansr;
ansl.mx=ansr.mx=ansl.sum=ansr.sum=0;
if(L<=mid)ansl=query(x<<1,l,mid,L,R);
if(R>mid)ansr=query(x<<1|1,mid+1,r,L,R);
if(ansl.mx!=ansr.mx)return ansl.mx>=ansr.mx?ansl:ansr;
ansl.sum=(ansl.sum+ansr.sum)%mod;
return ansl;
}
}T;
int main(){
scanf("%d%d",&n,&type);
for(int i=1;i<=n;++i)scanf("%d",&r[i]);
if(type){
T.modify(1,0,maxn-4,0,0,1);
for(int i=1;i<=n;++i){
node x=T.query(1,0,maxn-4,0,r[i]-1);
T.modify(1,0,maxn-4,r[i],x.mx+1,x.sum);
}
node x=T.query(1,0,maxn-4,0,maxn-4);
printf("%d\n%d\n",x.mx,x.sum);
}else{
memset(b,0x3f,sizeof(b));b[0]=0;
for(int i=1;i<=n;i++)b[lower_bound(b,b+n+1,r[i])-b]=r[i];
int ans=0;for(ans=1;ans<=n;++ans)if(b[ans+1]==b[n+1])break;
printf("%d\n",ans);
}
return 0;
}
C. 幻魔皇
\(fib\)的奇妙性质++
以下内容有不少借鉴于动动学长
这个\(fib\)树的性质
-
每层点数为\(fib\)
-
从第二层开始,黑点个数为\(fib\)
-
从第三层开始,白点个数为\(fib\)
-
对于每一个黑(白)点,如果它有一棵深度为k的子树,那么所有这些子树都是一样的。
为了方便,现在设
\(w[i]\)表示第\(i\)行白点的个数
\(b[i]\)表示第\(i\)行黑点的个数
\(sw[i]\)表示前\(i\)行白点个数
\(sb[i]\)表示前\(i\)行黑点个数
然后分两种情况
-
两个白点的\(lca\)为白点
-
两个白点的\(lca\)为黑点
对于第一种情况,可以发现其中一个白点就是\(lca\)
对于一棵根为白色的树,深度为\(i\)的白点数显然是\(w[i+1]\)
整棵树能够作为这样的树的根的节点一共有\(sw[n-i]\)个
那么\(ans[i]+=sw[n-i]*w[i+1]\)
对于第二种情况,考虑一棵根节点为黑色的树
枚举两侧点到根的距离\(i,j\)
(白色子节点的子树中的白点的距离为\(i\),黑色子节点的子树中的白点的距离为\(j\))
把黑点到白点的边断开,黑点看成白点,就可以看成两棵根为白色的树
也就是说,白色子节点中距离为\(i\)的白点有\(w[i]\)个,黑色根及黑色子节点中距离为\(j\)的白点有\(w[j+1]\)个
而这样的子树有\(sb[n−max(i,j)]\)个
所以\(ans[i+j]+=sb[n-max(i,j)]*w[i]*w[j+1]\)
那么对于这棵树,其对答案的贡献就是w[i]×w[j+1]
,而这样的子树的数量取决与i,j中更大的那一个,所以这样的子树有sb[n−max(i,j)]
code
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int mod=123456789;
int n,ans[10005];
int w[5005],b[5005],sw[5005],sb[5005];
int main(){
scanf("%d",&n);
w[1]=w[3]=1;sw[1]=sw[2]=1;sw[3]=2;
b[2]=b[3]=1;sb[2]=1;sb[3]=2;
for(int i=4;i<=n;++i){
w[i]=(w[i-1]+w[i-2])%mod;
b[i]=(b[i-1]+b[i-2])%mod;
sw[i]=(sw[i-1]+w[i])%mod;
sb[i]=(sb[i-1]+b[i])%mod;
}
for(int i=1;i<=n;++i)ans[i]=1ll*sw[n-i]*w[i+1]%mod;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
ans[i+j]=(ans[i+j]+1ll*sb[n-max(i,j)]*w[i]%mod*w[j+1]%mod)%mod;
for(int i=1;i<=n+n;++i)printf("%d ",ans[i]);
return 0;
}