首页 > 其他分享 >8.30 上午 becoder 模拟赛总结 & 题解

8.30 上午 becoder 模拟赛总结 & 题解

时间:2024-09-03 19:39:05浏览次数:14  
标签:gcd int 题解 LL long becoder 8.30 复杂度 define

T1 密码

当时想到解法了,却依然认为自己不会做,我真是个人才。

结论:对于 $\forall i \in [1,n)$ ,满足密码不是 $a_i$ 的因数,且密码是 $a_k$ 的因数,设满足条件的最小值为 $g$ 则答案为 $\frac{n}{g}$。

一种最好想的做法:枚举 $\gcd(a_k,n)$ 的因数作为 $g$,并枚举 $i \in [1,n)$,判断是否整除 $a_i$,如果是就不能作为答案。

以上是 60pts 做法,复杂度 $O(f(\gcd(a_k,n)k)$,其中 $f(x)$ 表示 $x$ 的因数个数。

贴个代码(60pts):

#define LL long long
LL n,k,a[250005];
LL gcd(LL x,LL y){return !y?x:gcd(y,x%y);}
bool check(LL x){
    for(int i=1;i<k;i++)
        if(a[i]%x==0)
            return 0;
    return 1;
}
int main(){
    LL g=gcd(n,a[k]),i=1;
    for(;i*i<=g;i++)  if(g%i==0&&check(i)) return printf("%lld\n",n/i),0;
    for(;i>=1;i--)  if(g%i==0&&check(g/i)) return printf("%lld\n",n/(g/i)),0;
}

接下来考虑进行优化。

已知对于 $a_i$,只有小于 $\gcd(a_k,n)$ 的因数会影响答案判断。

所以我们可以枚举 $\gcd(a_i,a_k,n)$ 中的质因数,然后搜索枚举 $m_i$ 的因数(详见代码),记录下哪些答案是不行的。

中途碰到标记过的数时就直接 return,这样复杂度可以保证在 $O(k \log V+\sqrt{\gcd(a_k,n)}+f(\gcd(a_k,n)))$,就可以过了。

贴下代码(100pts):

#define LL long long
map<LL,bool> mp;
LL n,k,cnt,pr[15],a[250005];
LL gcd(LL x,LL y){return !y?x:gcd(y,x%y);}
void dfs(LL x){
    if(mp.count(x))  return;mp[x]=1;
    for(int i=1;i<=cnt;i++)  dfs(x/pr[i]);
}
int main(){
    LL g=gcd(n,a[k]),tmp=g,i=1;
    for(i=2;i*i<=tmp;i++){
        if(tmp%i==0){
            pr[++cnt]=i;
            while(tmp%i==0)  tmp/=i;
        }
    }
    if(tmp>1)  pr[++cnt]=tmp;
    for(i=1;i<k;i++)  dfs(gcd(g,a[i]));
    for(i=1;i*i<=g;i++)  if(g%i==0&&!mp[i]) return printf("%lld\n",n/i),0;
    for(;i>=1;i--)  if(g%i==0&&!mp[g/i]) return printf("%lld\n",n/(g/i)),0;
}

T2 牛牛的猜球游戏

大水题,用一个前缀数组记录操作到第 $i$ 项时的杯子的摆放情况。

然后对于每次询问根据 $a_{l-1}$ 的值得到从 $a_{l-1}$ 执行到 $a_r$ 后的摆放情况

不多说了,直接上代码(100pts):

int n,m,a[100005][10],b[10];
int main(){
    scanf("%d%d",&n,&m),iota(a[0],a[0]+10,0);
    for(int i=1,x,y;i<=n;i++){
        copy(a[i-1],a[i-1]+10,a[i]);
        scanf("%d%d",&x,&y),swap(a[i][x],a[i][y]);
    }
    for(int i=1,l,r;i<=m;i++){
        scanf("%d%d",&l,&r);
        for(int j=0;j<10;j++)  b[a[l-1][j]]=j;
        for(int j=0;j<10;j++)  printf("%d ",b[a[r][j]]);
        printf("\n");
    }
}

T3 牛牛的凑数游戏

可以想到一种很简单的单次 $O(n log n)$ 做法:

将数组从小到大排序,然后枚举 $i$ 如果 $a_i \leq sum+1$,就把 $a_i$ 加到 $sum$ 中。

根据这种想法可以延伸出一种莫队的解法,也就是我赛上最开始写的 30pts 做法:

先按莫队的板子将所有询问离线排序,然后我们考虑回滚莫队。

莫队时记录当前的 $ans$ 值,并维护一个小顶堆的优先队列。

向莫队中添加元素时,我们就直接将元素塞进优先队列里面,然后每次 while 循环是否满足 $top() \leq sum+1$,是就弹出堆顶并累加答案。

这种解法的期望复杂度应该是 $O(n \sqrt{n} \log n)$,但是会被卡,因为回滚时有可能出现每次都重新弹出 $n$ 个数的情况。

所以最坏的复杂度仍为 $O(nm \log n)$,与暴力相同。

但我赛场上没有想到这个点,一直在死磕莫队,浪费了多到无法想象的时间

贴一下莫队的代码(30pts):

#define N 100005
#define LL long long
LL tmp,sum,ans[N];
int n,m,cnt,a[N],bel[N];
struct Node{int l,r,num;}s[N];
priority_queue<int,vector<int>,greater<int>> q,tq;
bool cmp(Node x,Node y){
    if(bel[x.l]!=bel[y.l])  return bel[x.l]<bel[y.l];
    return x.r<y.r;
}
void add(LL &sum,LL x,que &q){
    q.push(x);
    while(!q.empty()&&sum+1>=q.top())  sum+=q.top(),q.pop();
}
int main(){
    scanf("%d%d",&n,&m);
    int block=sqrt(n);
    for(int i=1;i<=n;i++)  scanf("%d",a+i),bel[i]=(i-1)/block+1;
    for(int i=1,l,r;i<=m;i++){
        scanf("%d%d",&l,&r);
        if(bel[l]==bel[r]){
            sum=0;while(!q.empty())  q.pop();
            for(int j=l;j<=r;j++)  add(sum,a[j],q);
            ans[i]=sum+1;
        }
        else  s[++cnt]={l,r,i};
    }
    sort(s+1,s+cnt+1,cmp);
    for(int i=1,l,r;i<=cnt;i++){
        if(bel[s[i].l]!=bel[s[i-1].l]){
            r=bel[s[i].l]*block,sum=0;
            while(!q.empty())  q.pop();
        }
        while(r<s[i].r)  add(sum,a[++r],q);
        l=bel[s[i].l]*block+1,tmp=sum,tq=q;
        while(l>s[i].l)  add(tmp,a[--l],tq);
        ans[s[i].num]=tmp+1;
    }
    for(int i=1;i<=m;i++)  printf("%lld\n",ans[i]);
}

后来我想通了,但只剩 1h 多一点时间了,虽然说这道题还是做出来了。

莫队不行于是考虑转变思路。

能够想到另外一种单次 $O(n \log V)$ 的解法:

使用树状数组维护对于任意 $x$,满足小于等于 $x$ 的数字和。

然后 while 循环每次加上所有小于等于当前 $sum+1$ 的数,没有就退出循环。

为什么复杂度是正确的呢?因为如果要加,$sum$ 最少都会翻倍,不加就退出了,自然是 log 的复杂度。

考虑怎么降低复杂度,有前面莫队的铺垫,应该会比较容易想到离线处理所有询问,至少我死磕莫队后脑子里就只剩离线了

先把所有询问离线出来然后对于每次 while 循环,我们先记录下上一次循环后的答案,并把每次询问拆成 $[1,l)$ 和 $[1,r]$ 两个区间,前减后加。

接下来 for 循环扫一遍看有没有询问的答案发生过变化,没有就直接 break,复杂度 $O(n \log^2 n)$。

贴一下最后的代码(100pts):

#define N 100005
#define LL long long
LL tr[N],lst[N],ans[N];
int n,m,tot,a[N],rk[N];
vector<pair<int,int>> v[N];
void update(int x,int y){while(x<=tot) tr[x]+=y,x+=x&(-x);}
LL query(int x,LL ans=0){while(x) ans+=tr[x],x-=x&(-x);return ans;}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)  scanf("%d",a+i),rk[i]=a[i];
    sort(rk+1,rk+n+1),tot=unique(rk+1,rk+n+1)-rk-1;
    for(int i=1;i<=n;i++)  a[i]=lower_bound(rk+1,rk+tot+1,a[i])-rk;
    for(int i=1,l,r;i<=m;i++){
        scanf("%d%d",&l,&r);
        v[l-1].push_back({i,-1}),v[r].push_back({i,1});
    }
    while(1){
        for(int i=1;i<=n;i++)  lst[i]=ans[i],ans[i]=tr[i]=0;
        for(int i=1;i<=n;i++){
            update(a[i],rk[a[i]]);
            for(auto [x,y]:v[i])  ans[x]+=y*query(upper_bound(rk+1,rk+tot+1,lst[x]+1)-rk-1);
        }
        for(int i=1;i<=n;i++)  if(lst[i]!=ans[i])  goto con;
        break;con:;
    }
    for(int i=1;i<=m;i++)  printf("%lld\n",ans[i]+1);
}

T4 牛牛的RPG游戏

先考虑一维做法,也就是 $\min(n,m) \leq 1$ 的部分分,设 $dp_i$ 表示走到第 $i$ 格的最大得分,得到转移式:

$
dp_i=max^{j \leq i}_{j=1}dp_j+buff_j*(i-j)+val_j
$

发现是斜率优化的经典转移,使用李超线段树优化即可。

关于我赛时 val 和 buf 输入反了这一件事,我的 20pts 啊!

一维部分分代码(20pts):

#define N 100005
#define LL long long
int n,m;
LL val[N],buf[N],dp[N];
struct Lichao_Segment_Tree{
    LL query(LL x)//查询x位置的最大值
    void insert(LL k,LL b)//插入一条直线
}LCT;//李超模板代码就不放了
int main(){
    for(int i=0;i<m;i++){
        if(!i)  LCT.insert(0,0);
        else{
            dp[i]=LCT.query(i)+val[i];
            LCT.insert(buf[i],dp[i]-i*buf[i]);
            if(val[i]<0)  dp[i]-=val[i];
        }
    }
}

接下来我们考虑扩展到二维上。

设 $dp_{i,j}$ 表示走到 $i$ 行 $j$ 列时的最大得分,可以列出转移式:

$
dp_{i,j}=max^{u\leq i,v\leq j}{u=1,v=1}dp+buff_{i,j}*(u+v-i-j)+val_{i,j}
$

这个时候直接用李超来优化就不是很好做了。神犇zyx可以忽略这句话

观察可以发现 $u \leq i,v \leq j$ 是一个二维偏序,联想以前学过的知识,cdq 分治可以解决这类问题。

接下来就可以 cdq 套李超线段树,理论上可能会很毒瘤,但实际写着其实并不会,时间复杂度 $O(n \log^2 n)$。

贴下代码(100pts):

#define N 100005
#define LL long long
int n,m,rt;
vector<LL> val[N],buf[N],dp[N];
struct Lichao_Segment_Tree{
    void clear()//清空
    LL query(LL x)//查询x位置的最大值
    void insert(LL k,LL b)//插入一条直线
}LCT;//李超模板代码就不放了
void cdq(int l,int r){
    if(l==r){
        LCT.insert(0,0);
        for(int i=1;i<=m;i++){
            dp[l][i]=max(dp[l][i],LCT.query(i));
            LCT.insert(buf[l][i],dp[l][i]-i*buf[l][i]+val[l][i]);
        }
        return LCT.clear();
    }
    int mid=(l+r)>>1;cdq(l,mid),LCT.insert(0,0);
    for(int j=1;j<=m;j++){
        for(int i=l;i<=mid;i++)
            LCT.insert(buf[i][j],dp[i][j]-(i+j)*buf[i][j]+val[i][j]);
        for(int i=mid+1;i<=r;i++)  dp[i][j]=max(dp[i][j],LCT.query(i+j));
    }
    LCT.clear(),cdq(mid+1,r);
}

多介绍一种来自 zyx 的方法:对每一行开一个李超线段树,并以 $\min(n,m)$ 作为行,暴力做纵向转移,时间复杂度 $O(nm \sqrt{nm} \log n)$。

标签:gcd,int,题解,LL,long,becoder,8.30,复杂度,define
From: https://www.cnblogs.com/tkdqmx/p/18395254

相关文章

  • 8.31 上午 becoder 模拟赛总结 & 题解
    T1四个质数的和赛场亲测搜索+小剪枝可以得到70pts。考虑$O(p(V)^2)$枚举任意两个质数的和,其中$p(V)$表示$V$以内质数的个数。然后开个数组记录下对于每种和的记录有多少种情况,查询时for循环扫一遍即可,详见代码。复杂度去掉质数筛$O(p(V)^2+tn)$,代码贴在下面(100pts)......
  • 8.31 下午 梦熊联盟 NOIP 模拟赛总结 & 题解
    T1北极星一个比较好想到的点是从后往前枚举数,计算得出它需要的操作次数,然后给所有前面的数都加上这个操作次数,这样就把每个数独立出来了。所以这道题就变成了如何快速通过这些操作得到一个指定的数。观察大样例的输出,发现每一个数都是11?1?1?的形式,其中问号为+或c,我们可......
  • 9.1 上午 becoder 模拟赛总结 & 题解
    T1货车运输Kruskal重构树模板,没什么好说的,不会的把自己重构了算了,跳过。T2Slagalica可以发现拼图1和2、3拼起来还是拼图1,拼图4和2、3拼起来也还是拼图4,这两种拼图还都不能和自己拼,所以我们可以看作只有拼图1和拼图4来做。对于边界拼图分别是5、7的情况,只有......
  • 8.31 晚上 ABC369 总结 & 题解
    打了一天的比赛。ABCD太水了,直接放代码链接得了,点字母就能看对应代码。E-SightseeingTour看范围$N$只有$400$,所以我们可以先用floyd搞出任意两点间的距离。对于每次询问,发现$K_i$只有$5$,所以可以直接深搜应该走哪座桥,和应该走到哪一端。时间复杂度$O(N3+QK_i......
  • 9.2 上午 becoder 模拟赛总结 & 题解
    T1加法最开始看了好久没想出来,先做T2去了,然后回来想了一会儿发现自己可能智商有点问题。看到求最小值最大,第一反应肯定是二分,那我们怎么应该怎么check呢?考虑顺次枚举序列$A$中的每一个数,然后如果这个数没有达到mid的要求,我们肯定是要添加区间的。那么我们怎么添加区......
  • 9.3 上午 becoder 模拟赛总结 & 题解
    T1能量获取简单的树形DP,设$dp_{i,j}$表示向$i$节点传递了$j$点能量并全部花费完后能激活的封印石的数量。显然有:$ans=\sum\max_{j=0}^{j\leqW_i}{dp_{i,j}}(i\inson_0)$,转移的初始状态为$dp_{i,E_i}=E_i$。设当前枚举到的节点为$x$,子节点为$y$,有经典树上背包转......
  • 算法专项—第十五届蓝桥杯程序设计题解
    前三道属于签到题目;后面才是有难度的!一、读书一本书共n页,小明计划第一天看x页,此后每一天都要比前一天多看y页,请问小明几天可以看完这本书?输入格式一行输入三个整数n,x,y(20≤n≤5000),(1≤x,y≤20),分别表示书的总页数、计划第一天看的页数以及此后每天都要比前一天多看的页数,整......
  • 『主席树』疯狂的颜色序列 题解
    又又又好久没写题解了青花-周传雄三月走过柳絮散落恋人们匆匆我的爱情闻风不动翻阅昨日仍有温度蒙尘的心事恍恍惚惚已经隔世遗憾无法说惊觉心一缩紧紧握着青花信物信守着承诺离别总在失意中度过记忆油膏反复涂抹无法愈合的伤口你的回头划伤了沉默那夜重......
  • SP1843 LEONARDO - Leonardo Notebook 题解
    题目传送锚点博客园食用更佳前置知识1前置知识2首先是判断有无解。先疯狂寻找完所有的环,然后判断是否是偶环,最后如果都是偶环,且偶环的路径数成双成对地出现,或全是奇环,就输出Yes,不然就直接pass掉,输出No。然后我们发现:这里竟然出现了置换群!!!为什么它是置换群呢?我们从群的定......
  • P10878 [JRKSJ R9] 在相思树下 III 题解
    Description给定一个长为\(n\)的序列\(a_{1\dotsn}\),需要对它进行两种操作共\(n-1\)次。对一个长度为\(l\)的序列\(b_{1\dotsl}\)进行一次操作将会把序列变为一个长为\(l-1\)的序列\(c_{1\dotsl-1}\):操作一中,\(\foralli\in[1,l),c_i=\max(b_i,b_{i+1})\);操作......