近期codeforces做题的总结?(不定期更新)

高考考完了,考上带学了,好耶(

开始尝试做一些题复建,这里是持续更新的近期做题记录

Educational Codeforces Round 106 (Rated for Div. 2)

D

题意:给定$ c,d,x $

求满足$ c*lcm(a,b)-d*gcd(a,b)=x $的对数考虑令$ a=A*gcd(a,b),b=B*gcd(a,b) $(重点,把枚举的$ a,b $从$ (a,b)=t $转化成$ (A,B)=1 $的$A$和$B$)

稍微整理一下就会发现

原式的形式是$c*A*B*gcd(a,b)-d*gcd(a,b)=x$

$(c*A*B-d)*gcd(a,b)=x$

考虑枚举$x$的因数(即枚举$gcd(a,b)$),令其为$y$

则$y+d$需要是$c$的倍数

接下来考虑在已知$A*B$的情况下如何求解$A$和$B$对应的组数

发现因为$(A,B)=1$

考虑$A*B$的质因数,如果$A$选了一个质因数,$B$就不能选这个质因数。

也就是说答案是$sum_{1<=i<=m}C_{m}^{i}$ 也就是$2^m$

现在问题是如何快速统计每个数字的因数个数

这里可以用线性筛先对于每个数字求解一个$minp$,表示该数字的最小质因数。

则$d_x = d_{frac{x}{minp}} + [frac{x}{minp} == minp] $

然后就做完了。

$O(n)$

Code:

#include <bits/stdc++.h>
using namespace std;
int T,c,d,x,Ans;
const int N=30000005;
bool IsPrime[30000007];
int Prime[30000007],Index,sp[30000007],val[30000007];
void Pre(){
    for (int i=2;i<=N;i++){
        if (!IsPrime[i]) Prime[++Index]=i,sp[i]=i;
        for (int j=1;j<=Index&&1ll*i*Prime[j]<=N;j++){
            IsPrime[i*Prime[j]]=true;sp[i*Prime[j]]=Prime[j];
            if (i%Prime[j]==0) break;
        }
    }
}
int main(){ 
    sp[1]=1;
    Pre();
    for (int i=2;i<=N;i++){
        if (sp[i/sp[i]]!=sp[i]) val[i]=val[i/sp[i]]+1;
        else val[i]=val[i/sp[i]];
    }
    scanf("%d",&T);
    while (T--){
        scanf("%d%d%d",&c,&d,&x);
        Ans=0;
        for (int i=1;i*i<=x;i++)
            if (x%i==0){
                int res=x/i;
                int Now=i;
                res+=d;Now+=d;
                if (Now%c==0) {Ans+=(1<<val[Now/c]);}
                if (res%c==0 && 1ll*i*i!=x) {Ans+=(1<<val[res/c]);}
            }
        printf("%d
",Ans);
    }
    return 0;
}

 E

题目链接:http://codeforces.com/contest/1499/problem/E


由于每个位置是否能填入某个字符只和它的上一位有关

于是我们就考虑设计一个$ dp $

$dp_{i,j,k}$表示$ a $串考虑了前$i$位,$ b $串考虑了前$ j $位,$ k=0/1 $表示当前最后一位是$ a $串或者$ b $串的。

显然转移有六种,上一位是$ b $串,放一个$ a $串的;上一位是$ b $串,放一个$ b $串的;上一位是$ a $串,放一个$ a $串的;上一位是$ a $串,放一个$ b $串的。

还有在这个位置开新$ a $和新$ b $

这六种转移都在代码里有体现。

但是这题还没做完

这个地方我们唯一没考虑的不合法情况是一个有东西的串配上一个空串的情况

于是我们采用一个双指针进行扫描,寻找$a$和$b$中有多少不同的子串,把不合法的答案减掉即可。

总效率$O(|a||b|)$

Code:

#include <bits/stdc++.h>
using namespace std;
const int fish=998244353;
int dp[1005][1005][2];
int ans;
string s1,s2;
int main(){
    cin>>s1;
    cin>>s2;
    int Len1=s1.length();
    int Len2=s2.length();
    for (int i=0;i<=Len1;i++)
        for (int j=0;j<=Len2;j++){
            if (i<Len1) dp[i+1][j][0]++;
            if (j<Len2) dp[i][j+1][1]++;
            if (0<i&&i<Len1&&s1[i-1]!=s1[i]) (dp[i+1][j][0]+=dp[i][j][0])%=fish;
            if (0<j&&i<Len1&&s1[i]!=s2[j-1]) (dp[i+1][j][0]+=dp[i][j][1])%=fish;
            if (0<j&&j<Len2&&s2[j-1]!=s2[j]) (dp[i][j+1][1]+=dp[i][j][1])%=fish;
            if (0<i&&j<Len2&&s2[j]!=s1[i-1]) (dp[i][j+1][1]+=dp[i][j][0])%=fish;
            (ans+=dp[i][j][0])%=fish;
            (ans+=dp[i][j][1])%=fish;
        }
    for (int l=0;l<Len1;l++){
        int r=l;
        while (r+1<Len1 && s1[r]!=s1[r+1]) r++;
        int Len=r-l+1;
        int val=(1ll*Len*1ll*(Len+1)/2)%fish*1ll*(Len2+1)%fish;
        ans=(1ll*(ans-val)+fish)%fish;
        //cout<<val<<" "<<ans<<endl;
        l=r;
    }
    for (int l=0;l<Len2;l++){
        int r=l;
        while (r+1<Len2 && s2[r]!=s2[r+1]) r++;
        int Len=r-l+1;
        int val=(1ll*Len*1ll*(Len+1)/2)%fish*1ll*(Len1+1)%fish;
        ans=(1ll*(ans-val)+fish)%fish;
        l=r;
    }
    cout<<ans<<endl;
    return 0;
}

Codeforces Round #700 (Div. 2)

D

链接:http://codeforces.com/contest/1480/problem/D2


题目讲的好绕

其实题目的意思是把$a$数列分成两个子数列,这两个子数列中,相邻的相同项可以进行合并,问最终最大的合并次数。

考虑贪心。

当前$a$序列和$b$序列,如果有元素和这个要放入的元素相同的话,则直接放入即可。

如果都不同的话,则放入下一个相同数字距离当前位置较远的那个序列里去。

实现的话,类似于序列自动机,引入一个$nxt$数组记录下一个与这个位置相同的位置在哪,$O(n)$逆向扫描更新即可。

#include <bits/stdc++.h>
using namespace std;
int nxt1,nxt2,ans;
int dp[100005],b[100005],a[100005],nxt[100005];
int N;
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        b[i]=N+1;
        nxt[i]=N+1;
        scanf("%d",&a[i]);
    }
    b[0]=N+1;
    nxt[0]=N+1;
    for (int i=N;i>=1;i--){
        nxt[i]=b[a[i]];
        b[a[i]]=i;
    }
    int x,y;
    x=0;y=0;
    for (int i=1;i<=N;i++){
        if (a[x]==a[i]) {x=i;} else
        if (a[y]==a[i]) {y=i;} else
        if (nxt[x]>nxt[y]) {ans++;x=i;}
        else {ans++;y=i;}
        //cout<<x<<" "<<y<<endl;
    }
    cout<<ans;
    return 0;
}

Codeforces Round #706 (Div. 2)

C

链接:http://codeforces.com/contest/1496/problem/C

直观理解一下,应该是y轴大的点配x轴大的点,x轴小的点配y轴小的点

xjb证明一下(
考虑四个点,其中两个点在$y$轴上,两个点在$x$轴上。

设它们的数值分别为$a,b,c,d$

其中$a<b,c<d$

如果$a$配$c$,$b$配$d$产生的贡献是

$dis_1=sqrt{a^2+c^2}+sqrt{b^2+d^2}$

如果$a$配$d$,$b$配$c$产生的贡献是

$dis_2=sqrt{a^2+d^2}+sqrt{b^2+c^2}$

考虑$dis_2^2-dis_1^2$的结果

发现化简完是$(a-b)(c-d)$,显然这个式子是大于$0$的

于是发现$dis_2$是比$dis_1$要大的

于是选择$dis_1$更优。

$O(n log n)$

Code

#include <bits/stdc++.h>
using namespace std;
int x[100005],y[100005],N,T;
double dis(int a,int b){
    return (sqrt(1ll*a*1ll*a+1ll*b*1ll*b));
}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        int cnt1=0,cnt2=0;
        for (int i=1;i<=2*N;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            if (u<0) u=abs(u);
            if (v<0) v=abs(v);
            if (u==0) y[++cnt1]=v;
            else x[++cnt2]=u;
        }
        sort(y+1,y+N+1);
        sort(x+1,x+N+1);
        double ans=0;
        for (int i=1;i<=N;i++)
            ans=ans+dis(x[i],y[i]);
        printf("%.10lf
",ans);
    }
    return 0;
}

Codeforces Round #722 (Div. 2)

D

链接:http://codeforces.com/contest/1529

定义$dp_i$表示$2i$组点的符合答案的方案数。

考虑一个$x$点,它和$1$点配对。

如果$x>n$,剩下$x-n-1$对没配的,答案就是$x-n-1$

如果$x<n$,则线段必定是$n$的因数。

发现其实最后答案等价于

$dp_i$=$sum dp_j + D(i)$

$D(i)$预处理即可。

#include <bits/stdc++.h>
using namespace std;
const int fish=998244353;
int N;
int dp[1000005];
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++)
        for (int j=i+i;j<=N;j+=i)
            dp[j]++;
    int Sum=1;
    dp[0]=1;
    for (int i=1;i<=N;i++){
        dp[i]=(dp[i]+Sum)%fish;
        (Sum+=dp[i])%=fish; 
    }
    cout<<dp[N];
    return 0;
}

Codeforces Round #726

D

链接:http://codeforces.com/contest/1537/problem/D


当前是一个数字$N$,考虑一个因数$D$

选完这个数字后,剩下的数字就是$N-D$

此时若$D$是一个质因数的话,则之后就会发现因为$ N-D ≡ 0(mod M)$

则有$M|N$,$M|D$

于是发现因为$D$是一个质因数,则之后只能继续取$D$

如果$N$是一个奇数的话

发现能取$frac{N}{D}$,这必然是一个奇数。

于是$Bob$获胜

再考虑一个偶数的情况

同理可以发现这时候$Alice$必胜

但是我们需要考虑一个特殊情况(我没想到,呜呜)

如果$N$是一个$2^k$的形式的话

如果我取一个数字,让它变成了不是$2^k$的形式,我们就会发现它一定是个偶数,且有一个质因子。

而我们上面论证过,该情况是先手获胜

于是就可以发现两人都会尽量的避免这种情况。

那么要如何避免呢?

我要再次构造一个$2^k$的形式。

于是两个人就会不断地轮流构造这样的$2^m$的形式,谁先构造不出来谁就输了。

于是就算一下这个$k$然后考虑奇偶性即可。

$O(T log N)$

#include <bits/stdc++.h>
using namespace std;
int T,N;
const int a[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912};
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        if (N%2){
            printf("Bob
");
            continue;
        }
        bool pd=false;
        for (int i=1;i<30;i++)
            if (N==a[i]){
                if (i%2==0) printf("Alice
");
                else printf("Bob
");
                pd=true;
                break;
            }
        if (pd) continue;
        printf("Alice
");
    }
}

Codeforces Round #735 (Div. 2)

D

构造题,我构造好菜,呜呜

奇数+奇数=偶数,奇数+偶数=奇数

于是我们就可以构造这种东西

$aaaa....aaa([frac{n}{2}]个a)baa....aaaa([frac{n}{2}]+1个a)$

这是对于给定的$N$是偶数来说的

如果是奇数,往中间多插一个$c$就行。

#include <bits/stdc++.h>
using namespace std;
int N;
int T;
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        int K;
        N--;
        if (N%2==1) K=(N+1)/2;
        else K=N/2;
        for (int i=1;i<=K;i++)
            printf("a");
        printf("b");
        if (N==0){
        printf("
");
        continue;
        }
        if (N%2==0) {printf("c");N--;}
        for (int i=1;i<=N-K;i++)
            printf("a");
        printf("
");
    }
    return 0;
}

Codeforces Round #694 (Div. 2)

D

先考虑没有操作的时候这个答案怎么算

$frac{lcm(x,y)}{gcd(x,y)}=frac{xy}{gcd^2(x,y)}$为完全平方数

其实也就是$xy$是一个完全平方数

再换句话说,就是每一个相同的质因数的次方数的奇偶性是一样的。

稍微哈希一下,找个最大值就行了。

接下来考虑引入操作带来的影响。

可以发现的是,这个操作对于一个个数是奇数的组是没有影响的

对于一个个数是偶数的组,可以发现在做完一次操作之后,所有个数为偶数的组自身会形成完全平方数,也就是说可以被扔进一个新产生的组里。

显然,这个结论对于$1$也成立

于是我们设置一个$Ans$计算一下这个新产生的组,并且和原本的$ans$进行一下大小比较就可以了。

$O(n log n)$

#include <bits/stdc++.h>
using namespace std;
int T,N,a[1000005],q,w,Index,c[1000005],Ans,ans1,Prime[1000005],sp[1000005];
bool IsPrime[1000005];
map<int,int> Count;
void Pre(){
    for (int i=2;i<=1000000;i++){
        if (!IsPrime[i]) Prime[++Index]=i,sp[i]=i;
        for (int j=1;j<=Index&&1ll*i*Prime[j]<=1000000;j++){
            IsPrime[i*Prime[j]]=true;sp[i*Prime[j]]=Prime[j];
            if (i%Prime[j]==0) break;
        }
    }
}
void Solve(){
    for (int i=1;i<=N;i++){
        int Hash=1;
        int x=a[i],cnt=0;
        while (x>1){
            cnt=0;
            int nw=sp[x];
            while (x%nw==0){
                cnt++;
                x/=nw;
            }
            if (cnt%2==1) Hash=Hash*nw;
        }
        Count[Hash]++; 
        ans1=max(ans1,Count[Hash]);
        a[i]=Hash;
    }
    for (int i=1;i<=N;i++)
        if (a[i]==1||!(Count[a[i]]&1)) Ans+=Count[a[i]],Count[a[i]]=0;
    for (int i=1;i<=N;i++)
        Count[a[i]]=0;
}
int main(){
    Pre();
    scanf("%d",&T);
    while (T--){
        ans1=Ans=0;
        scanf("%d",&N);
        for (int i=1;i<=N;i++)
            scanf("%d",&a[i]);
        scanf("%d",&q);
        Solve();
        for (int i=1;i<=q;i++){
            long long w;
            scanf("%lld",&w);
            if (w==0) printf("%d
",ans1);
            else printf("%d
",max(ans1,Ans));
        }
    }
}

Codeforces Round #727 (Div. 2)

D

链接:http://codeforces.com/contest/1539/problem/D


贪心。

先把所有物品按照$b_i$从小到大排序。

发现其实吧,$b_i$在越前面的是越容易达到打折的条件的。

然后$b_i$在后面的比较难达到打折条件的。

于是我们可以得到这些结论:
1.如果当前已经买到了$b_i$,直接买就行了

2.如果当前不够$b_i$,我们就考虑从$b_i$最大的那些买,这不会使答案更劣。

于是就可以用一个双指针来实现这个贪心。

$O(n)$

#include <bits/stdc++.h>
using namespace std;
struct Node{
    long long a,b;
}Items[100005];
int pd(Node a,Node b){
    return a.b<b.b;
}
int N;
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++)
        scanf("%lld%lld",&Items[i].a,&Items[i].b);
    sort(Items+1,Items+N+1,pd);
    long long l=1,r=N,Now=0,ans=0;
    while (l<=r){
        if (Items[l].b<=Now){
            ans+=Items[l].a;
            Now+=Items[l].a;
            l++; 
        }
        else{
            if (Items[r].a>=Items[l].b-Now){
                ans+=(Items[l].b-Now)*2;
                Items[r].a-=(Items[l].b-Now);
                Now+=(Items[l].b-Now);
            }
            else{
                ans+=Items[r].a*2;
                Now+=Items[r].a;
                r--;
            }
        }
    }
    cout<<ans;
    return 0;
}

Codeforces Round #732 (Div. 2)

D

链接:http://codeforces.com/contests

是跳棋,好耶!

显然有下面两个事实成立。

1.一组11可以不断想左/向右跳动。

2.两组11之间的相对顺序不会调换

于是其实这个问题就变成了

有$k$组$11$和一些$0$,问能形成的序列方案。

隔个板就可以了。

#include <bits/stdc++.h>
using namespace std;
const int fish=998244353;
int T,N,mo,mz;
bool vis[100010];
int fac[100010],inv[100010];
string ss;
int Pow(int x,int y){
    int ans=1;
    for (int i=y;i;i>>=1){
        if (i&1) ans=(1ll*ans*x)%fish;
        x=1ll*x*x%fish; 
    }
    return ans;
}
int C(int n,int r){
    if (n<r) return 1;
    return 1ll*fac[n]*inv[r]%fish*inv[n-r]%fish;
}
int main(){
    fac[0]=1;
    //cout<<Pow(2,5)<<endl;
    for (int i=1;i<=100004;i++) fac[i]=(1ll*fac[i-1]*i)%fish;
    inv[100004]=Pow(fac[100004],fish-2);
    for (int i=100003;i>=1;i--)
        inv[i]=1ll*inv[i+1]*(i+1)%fish;
    inv[0]=1;
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        cin>>ss;
        memset(vis,false,sizeof(vis));
        mo=0,mz=0;
        for (int i=0;i<N;i++){
            if (ss[i]=='1'&&ss[i+1]=='1'&&!vis[i]) {
                vis[i]=true;vis[i+1]=true;
                mo++;
            }
            if (ss[i]=='0') mz++;
        }
        //cout<<mo<<" "<<mz<<endl;
        printf("%d
",C(mz+mo,mo));
    }
    return 0;
}

Codeforces Round #736 (Div. 2)

D

链接:http://codeforces.com/contest/1549

把连续的同余转化成相邻项差在模意义下为$0$

模意义下为$0$意味着整除

构造一个新数列$b_n$,它存储相邻项差。

于是这个问题就变成:最长的连续$gcd$不为$1$

我们可以用一个线段树来统计区间的$gcd$

然后用一个双指针来实现这个统计最值的操作。

#include <bits/stdc++.h>
using namespace std;
int N;long long a[200005],T,b[200005],Tree[1000005];
long long Build(int Now,int l,int r){
    if (l==r) {Tree[Now]=a[l];return Tree[Now];}
    int mid=(l+r)>>1;
    Build(Now<<1,l,mid);Build(Now<<1|1,mid+1,r);
    return (Tree[Now]=__gcd(Tree[Now<<1],Tree[Now<<1|1]));
}
long long query(int Now,int l,int r,int L,int R){
    //cout<<Now<<endl;
    if (L<=l&&r<=R) return Tree[Now];
    int mid=(l+r)>>1;
    if (L<=mid && mid<R)
        return __gcd(query(Now<<1,l,mid,L,R),query(Now<<1|1,mid+1,r,L,R));
    else if (L<=mid) return query(Now<<1,l,mid,L,R);
    else if (mid<R) return  query(Now<<1|1,mid+1,r,L,R);
}
int main(){
    scanf("%d",&T);
    int l,r,ans;
    while (T--){
        scanf("%d",&N);
        for (int i=1;i<=N;i++)
            scanf("%lld",&b[i]);
        for (int i=1;i<=N-1;i++)
            a[i]=abs(b[i+1]-b[i]);
        N--;
        if (N==0){
            printf("1
");
            continue;
        }
        Build(1,1,N);
        l=1,r=1,ans=0;
        for (;r<=N;r++){
            while (query(1,1,N,l,r)==1&&l<r) l++;
            if (l==r&&a[l]!=1) ans=max(ans,2);
            else if (l==r) ans=max(ans,1);
            else ans=max(ans,r-l+2);
        }
        if (ans==0) printf("1
");else
        printf("%d
",ans);
    }
    return 0;
}

Codeforces Round #741 (Div. 2)

D

链接:http://codeforces.com/contest/1562

题目为:删除l,r中的多少节点,可以使sum[r]=sum[l]
观察发现 答案只有0,1,2
考虑一个节点的删去会带来哪些影响
也就是:该节点后续的 +1 变成 -1,-1 变成 +1
一个+1 变成 -1 对前缀和的影响是使得前缀和-2
一个-1 变成 +1 对前缀和的影响是使得前缀和+2
也就是说 其实这个位置的修改对答案的影响是:
abs(1的数量- -1的数量)*系数*2
其中系数取决于1比0多还是0比1多。
而这个 1的数量 - -1的数量
其实就是sum[r]-sum[x],x是我们选定的一个位置。
因为我们删去了一个节点,所以前缀和会相应的+1/-1
当sum[r] == sum[l]的时候,显然我们并不需要删去任何节点。
当sum[r] != sum[l]时,我们进行分类讨论:
找到一个位置,使得sum[r]-2*sum[x]+1=sum[l]
找到一个点:
sum[r]-sum[x]+1=sum[x]-sum[l]
也就是找到一个点,使得两段和恰好差1
如果这两段的差是一个奇数,那么显然这个点是可以被找到的,因为它是连续的
如果两段的差是一个偶数的,那么我们可以先随便删除r这个点,使得两个点的差变为奇数,然后再找到一个这样的点。
所以答案只有0,1,2。

答案的位置二分去找就行了。

#include <bits/stdc++.h>
using namespace std;
int N,T,Q,sum[300005],nw,xs;
char c; 
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d%d",&N,&Q);            
        for (int i=0;i<N;i++){
            cin>>c;
            if (c=='+') nw=1;else nw=-1;
            if ((i+1)%2==1) xs=1;else xs=-1;
            sum[i+1]=sum[i]+xs*nw;
        }
        for (int i=1;i<=Q;i++){
            int l,r;
            scanf("%d%d",&l,&r);
            l--,r--;
            if (sum[l]==sum[r+1]){ 
            printf("0
");
            continue;
            }
            if (abs(sum[r+1]-sum[l])%2==1) printf("1
");
            else{
            printf("2
");
            printf("%d ",r+1);
            r--;
            }
            int L=l,R=r+1;
            int nd=min(sum[l],sum[r+1])+abs(sum[r+1]-sum[l])/2; 
            while (R>L+1){
                int mid=(L+R)/2;
                if ((sum[mid]<=nd)==(sum[L]<=nd)) L=mid;
                else R=mid;
            }
            printf("%d
",L+1);
        }
    }
    return 0;
}

Codeforces Round #701 (Div. 2)

D

链接:http://codeforces.com/contest/1485/problem/D

构造题,没想出来,我tcl(

发现$a_{i,j}$的大小限制是16

发现$lcm(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)=720720$

取$720720$可以对任意的$a$都满足条件$2$。

对于条件$3$,我们交替的填入$a_{i,j}^4$即可。

(虽然但是我连720720都没想到,呜呜)

#include <bits/stdc++.h>
using namespace std;
int N,M;
int main(){
    scanf("%d%d",&N,&M);
    for (int i=0;i<N;i++){
        for (int j=0;j<M;j++){
            int x;
            scanf("%d",&x);
            if (!((i+j)&1)) printf("720720 ");
            else printf("%d ",720720+x*x*x*x);
        }
        printf("
");
    }
    return 0;
}
蒟蒻のOIer 萌新の东方玩家 睿智の休伯利安清洁工 参上
原文地址:https://www.cnblogs.com/si--nian/p/15344741.html