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);
}