noip模拟13[工业题·卡常题·玄学题]

noip模拟13 solutions

这一次考得是真不好,因为时间分配的太不合理了

第一题直接干了半个小时,啊啊后面的就没有时间仔细想,导致后面的题会做的也白扔掉啦

所以下次考试注意时间分配问题

·

T1 工业题

这个就是我猛干两个小时的题,考场上的时候,

一开始觉得是个矩阵快速幂,在那里推了半天,得到了一个结论:这个题不可做

就直接画图找规律,就画出来一个杨辉三角

在半个小时的时候,我突然想到,杨辉三角之前好像用到过,用来求取组合数,

思路就在这里断掉了,本来下一秒我应该想到,我也可以用组合数来求杨辉三角,但是

我直接放弃,开始在杨辉三角上找规律,就怎么也优化不了复杂度

后来一个半小时的时候,才恍然大悟,然后码了半个小时,90,因为\(inv[0]\)没有赋值

其实这个题有个比较简单的思路,将这个矩阵看作是一张图,

每一个有值的位置的数都要走到\((n,m)\)去,只能向下或者向右走

每向右走一格就要\(*a\),每向下走一格就要\(*b\)

还可以根据组合数来找到一共有多少种路径,答案就出来了

按照上面的思路是这样的:

\(f[i][0]×C(n-i+m-1,n-i)×a^{m}×b^{n-i}\)

\(f[0][i]×C(n+m-i+1,m-i)×a^{m-i}×b^{n}\)

然鹅我并不是这样打的,所以请不要看我的代码啦

AC_code


#include<bits/stdc++.h>
using namespace std;
#define re register long long
#define ll long long
const ll mod=998244353;
const int N=3e5+10;
ll n,m,a,b;
ll x[N],y[N];
ll jc[N*2],inv[N*2];
ll ksm(ll x,ll y){
	ll ret=1;
	x%=mod;y%=mod;
	while(y){
		if(y&1)ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
ll C(ll x,ll y){
	if(x==0||y==0)return 1ll;
	return jc[x]*inv[x-y]%mod*inv[y]%mod;
}
ll ans;
signed main(){
	scanf("%lld%lld%lld%lld",&n,&m,&a,&b);
	a%=mod;b%=mod;
	for(re i=1;i<=n;i++)scanf("%lld",&x[i]),x[i]%=mod;
	for(re i=1;i<=m;i++)scanf("%lld",&y[i]),y[i]%=mod;
	jc[1]=1ll;inv[0]=1;
	for(ll i=2;i<=n+m+10;i++)jc[i]=(jc[i-1]*i)%mod;
	inv[n+m]=ksm(jc[n+m],mod-2);inv[1]=1;
	for(ll i=n+m-1;i>=2;i--)inv[i]=(inv[i+1]*(i+1))%mod;
	ll tmp=0,bas=m-1,aa=m,bb=0;
	for(re i=n;i>=1;i--){
		ans=(ans+C(bas,tmp)*x[i]%mod*ksm(a,aa)%mod*ksm(b,bb)%mod)%mod;
		bb++;tmp++;bas++;
	}
	tmp=0;bas=n-1;aa=0;bb=n;
	for(re i=m;i>=1;i--){
		ans=(ans+C(bas,tmp)*y[i]%mod*ksm(a,aa)%mod*ksm(b,bb)%mod)%mod;
		aa++;bas++;tmp++;
	}
	printf("%lld",ans);
}

·

T2 卡常题

当我们看完题解之后,所有的题都变得好简单

真的看到这个题之后,我第一想法是最小生成树,直接一个并查集走起

测完第一个数据发现对了,心里窃喜,但是第二个就\(WA\)掉啦,就心灰意冷的走掉了

毕竟我现在没多少时间啦,第一题干了俩小时,,后来讨论题的时候,瞬间就醒悟啦

首先我读题的时候读错了,没有看到这是一个联通图

这样的话,我们就将Y方点,看作是在X与X的连边上的点,那这样的话,我们就可以将整张图构建成一个带有一个环的树

因为是联通图而且边数和点数都是n,所以只有一个环

这样我们就可以在环上随意断掉一个边,然后分别以这条边的两个端点为根,dp搞一搞就好啦

这个dp转移非常好想,请自行思考

但是要注意的是,断掉的这条边上还有一个Y,所以这两个端点也就是这两个根,必须选上其中一个,

就是在dp的时候根是必须要选的,然后在这两个结果中选取最小值就行啦

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e6+5;
int n,a,b;
int to[N*2],nxt[N*2],head[N],rp;
int val[N];
void add_edg(int x,int y){
	to[++rp]=y;
	nxt[rp]=head[x];
	head[x]=rp;
}
bool vis[N];
int rt1,rt2,dp1[N][2],dp2[N][2],ji;
void dfs_find(int x,int f){
	vis[x]=true;
	for(re i=head[x],tmp;i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		if(vis[y]){
			rt1=x;rt2=y;
			break;
		}
		dfs_find(y,x);
		tmp=i;
	}
	return ;
}
void dfs_1(int x,int f){
	int flag=0;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		flag=1;
		dfs_1(y,x);
		dp1[x][1]+=min(dp1[y][0],dp1[y][1]);
		dp1[x][0]+=dp1[y][1];
	}
	dp1[x][1]+=val[x];
	if(!flag)dp1[x][1]=val[x],dp1[x][0]=0;
}
void dfs_2(int x,int f){
	int flag=0;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		flag=1;
		dfs_2(y,x);
		dp2[x][1]+=min(dp2[y][0],dp2[y][1]);
		dp2[x][0]+=dp2[y][1];
	}
	dp2[x][1]+=val[x];
	if(!flag)dp2[x][1]=val[x],dp2[x][0]=0;
}
signed main(){
	scanf("%d%d%d",&n,&a,&b);
	for(re i=1,x,y;i<=n;i++){
		scanf("%d%d",&x,&y);
		val[x]+=a;val[y]+=b;
		add_edg(x,y);add_edg(y,x);
	}
	dfs_find(1,0);
	for(re i=head[rt1],tmp;i;i=nxt[i]){
		if(to[i]==rt2){
			if(i==head[rt1])head[rt1]=nxt[i];
			else nxt[tmp]=nxt[i];
			break;
		}
		tmp=i;
	}
	for(re i=head[rt2],tmp;i;i=nxt[i]){
		if(to[i]==rt1){
			if(i==head[rt2])head[rt2]=nxt[i];
			else nxt[tmp]=nxt[i];
			break;
		}
		tmp=i;
	}
	dfs_1(rt1,0);
	dfs_2(rt2,0);
	printf("%d",min(dp1[rt1][1],dp2[rt2][1]));
}

·

T3 玄学题

这个题几乎是这场考试中最好解决的题了吧

可惜我只留给了它20min,导致我只打了个暴力,拿到了30pts

其实我在考场上的时候,想到了这个题和完全平方数有关,然而。。。

观察式子发现,指数的奇偶性是我要关注的重点

这个题的确和完全平方数有关,因为只有完全平方数的因子才可能是奇数个(这样才可能对答案有贡献

\(i*j\)为完全平方数,可以这样想一想:令\(i=p*q^2\)这里\(p\)中不含任何平方因子

那么,有贡献的\(j\),也就是\(i*j\)为完全平方数的时候,\(j=p*r^2\),

为什么可以这样写呢?因为\(i*j\)没有非完全平方因子,所以j中必然含有p,且其他的都是完全平方因子

于是问题转化为了对于每个i,我们要找到他的p,然后有贡献的j就有\(\sqrt{\dfrac{m}{p}}\)

这里挺好理解的,包含p这个因子的有\(\dfrac{m}{p}\)个,里面含有完全平方的个数就是开根号(注意全部向下取整)

一开始我就傻不啦叽的去写了个\(O(n\sqrt{n})\)的算法,说白了就是直接枚举n,然后硬找p

50pts,根号复杂度


#include<bits/stdc++.h>
using namespace std;
#define re register long long
#define ll long long
ll n,m;
ll ans;
signed main(){
	scanf("%lld%lld",&n,&m);
	for(re i=1;i<=n;i++){
		int tmp=i;
		for(re j=sqrt(n);j>=2;j--)
			if(tmp%(j*j)==0)
				tmp/=(j*j);
		int res=sqrt(m/tmp);
		if(res%2==0)ans+=1;
		else ans-=1;
	}
	printf("%lld",ans);
}

我们还是要继续观察这个p,令它为f[i],就表示i的非平方因子乘积

显然这个f[i],是关于素数的函数,是积性函数,我们就可以拿出我们的线性筛大法了

然后就出现了复杂度极小的\(O(n)\)算法

AC_code


#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e7+5;
int n;
ll m;
ll ans;
ll prime[N],cnt,f[N];
bool vis[N];
signed main(){
	scanf("%d%lld",&n,&m);
	f[1]=1;ans=(((int)sqrt(m)%2==0)?1:-1);
	for(re i=2;i<=n;i++){
		if(!vis[i])prime[++cnt]=i,f[i]=i;
		int tmp=sqrt(m/f[i]);
		if(tmp%2)ans--;
		else ans++;
		for(re j=1;j<=cnt&&i*prime[j]<=n;j++){
			vis[prime[j]*i]=true;
			if(i%prime[j]==0){
				if(f[i]%prime[j]==0)f[prime[j]*i]=f[i]/prime[j];
				else f[prime[j]*i]=f[i]*prime[j];
				break;
			}
			f[prime[j]*i]=f[i]*prime[j];
			
		}
	}
	printf("%lld",ans);
}
posted @ 2021-07-13 16:33  fengwu2005  阅读(64)  评论(0编辑  收藏  举报