Codeforces Round #511 (Div. 1) 题解

在学校熬夜打$cf$找死啊。

A - Enlarge GCD

先求个$gcd$,然后对于每个$a[i]$都除以$gcd$。

之后我们只需要统计每个质数整除的个数即可。

因为带上所有数的$gcd$一定是最优的。

#include <bits/stdc++.h>
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define for1(a,b,i) for(int i=a;i<=b;++i)
#define FOR2(a,b,i) for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;
inline int read() {
    int f=1,sum=0;
    char x=getchar();
    for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1;
    for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0';
    return f*sum;
}

#define M 1000005
#define N 15000000
int n;
int a[M],f[M*20],prime[N+5];
bool notprime[N+5];

inline bool check(int x) {
    for1(1,n,i) if(x!=a[i]) return 0;
    return 1;
}

inline int gcd(int x,int y) {return y?gcd(y,x%y):x;}

inline void get_prime() {
    for1(2,N,i) {
        if(!notprime[i]) prime[++prime[0]]=i;
        for1(1,prime[0],j) {
            if(i*prime[j]>N) break;
            notprime[i*prime[j]]=1;
            if(!(i%prime[j])) break;
        }
    }
}

int main() {
    //freopen("a.in","r",stdin);
    //freopen("mine.out","w",stdout);
    get_prime();
    n=read();
    for1(1,n,i) a[i]=read();
    int sum=a[1];
    for1(2,n,i) sum=gcd(sum,a[i]);
    if(check(sum)) return puts("-1"),0;
    int Max=0;
    for1(1,n,i) {
        a[i]/=sum,++f[a[i]];
        Max=max(Max,a[i]);
    }
    int ans=0;
    for1(2,Max,i) {
        if(notprime[i]) continue;
        int x=i;
        int tot=0;
        while (x<=Max) tot+=f[x],x+=i;
        ans=max(ans,tot);
    }
    cout<<n-ans<<endl;
}

B - Little C loves 3 ||

做这种题我一直都是完蛋。

不想找一个规律,但是自己又不能证明。

主要就是$nm$都很大的时候都可以填到最满,然后特判几个不满足的。

#include <bits/stdc++.h>
#define for1(a,b,i) for(int i=a;i<=b;++i)
#define FOR2(a,b,i) for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;

int main() {
    //freopen("a.in","r",stdin);
    ll n,m;
    cin>>n>>m;
    if(n>m) swap(n,m);
    ll ans=0;
    if(n==1) ans=m/6*6+max(0ll,m%6-3)*2;
    else if(n==2) {
        ans=n*m;
        if(m==2) ans=0;
        if(m==3||m==7) ans-=2;
    }
    else ans=n*m-(n*m&1);
    cout<<ans<<endl;
}

C - Region Separation

显然对于每一种$val$的划分只有一种方案。

并且如果可以划分成$x$块和$d$块,且$d|x$,那么一定$d$是可以作为$x$的下一级的。

难点就在于我们怎么判断分成$x$块是否可行。

设$sum[i]$为$i$的子树的权值和。

条件:有$x$个点满足$sum[i]%(sum[1]/x)==0$。

因为我们划分之后的方案也可以表示成一棵树。

且其中每个点都是原树上的一个联通块,且权值为$sum[1]/x$。

然后就可以$O(n*logn)$了。

#include <bits/stdc++.h>
#define for1(a,b,i) for(int i=a;i<=b;++i)
#define FOR2(a,b,i) for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;

#define M 1000005
#define mod 1000000007
int n;
ll val[M];
int fa[M],vis[M],f[M];

inline ll gcd(ll x,ll y) {return y?gcd(y,x%y):x;}
inline void inc(int &x,int y) {x+=y,x-=x>=mod?mod:0;}

int main () {
    //freopen("a.in","r",stdin);
    scanf("%d",&n);
    for1(1,n,i) scanf("%lld",val+i);
    for1(2,n,i) scanf("%d",fa+i);
    FOR2(n,2,i) val[fa[i]]+=val[i];
    for1(1,n,i) {
        ll d=val[1]/gcd(val[i],val[1]);
        if (d<=n) ++vis[d];
    }
    FOR2(n,1,i) {
        if(vis[i]) for(int j=2*i;j<=n;j+=i) vis[j]+=vis[i];
    }
    FOR2(n,1,i) {
        if(vis[i]!=i) continue;
        f[i]=1;
        for(int j=2*i;j<=n;j+=i) inc(f[i],f[j]);
    }
    printf("%d
",f[1]);
}

D - Intervals of Intervals

好题。

显然的是我们可以二分出最小的权值。

统计个数可以利用单调性。

现在的问题就是怎么线性的算出满足$val>=mid$的区间的个数。

以及知道$Min$之后统计最终的答案。

题解的思路是依次加入每个区间,并且维护每个区间的出现时间的最大值。

显然这样用$set$搞一边是$O(n*logn)$。

我们还顺便统计出了$i$会和前边的那些区间在哪些位置重复(最靠右的)。

在统计个数时,我们只需要线性的维护加一个区间和删一个区间后的贡献。

加入一个最右边区间时,我们便可以加上它独有的区间的长度以及上次重复位置小于$l$的长度。

在删除一个最左边的区间时,因为它之前没有区间对他影响了,我们可以直接减去它的长度。

注意因为这个最左区间的存在导致了后边一些重复的区间无法作出贡献,这个可以开一个数组统计一下即可。

算答案类似的思路,我用了树状数组统计。

#include <bits/stdc++.h>
#define for1(a,b,i) for(int i=a;i<=b;++i)
#define FOR2(a,b,i) for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;

#define M 500005

struct point {
    int x,id;
};

struct node {
    int l,r,t;
    
    inline void in() {
        scanf("%d%d",&l,&r),--r;
    }
    inline bool operator <(const node &a) const {
        return l<a.l;
    }
}a[M];

int n,K;
ll sta[2][M];
int f[M],shu[M],pos[M];
set <node> cc;
vector <point> buc[M];

inline void T_add(int x,int y) {
    ll num=1ll*x*y;
    for(;x<=n;x+=x&-x) sta[0][x]+=y,sta[1][x]+=num;
}
inline ll query(int opt,int x) {
    ll sum=0;
    for(;x;x-=x&-x) sum+=sta[opt][x];
    return sum;
}

inline int calc_(int x) {
    memset(shu,0,sizeof(shu));
    int ans=0,cnt=0,head=1;
    for1(1,n,i) {
        ans+=f[i];
        int size=buc[i].size();
        for1(1,size,j) {
            point t=buc[i][j-1];
            if(t.id<head) ans+=t.x;
            else shu[t.id]+=t.x;
        }
        while (head<=i&&ans>=x) {
            /*
            因为前面删了一些点,所以这里的贡献不只是当时的贡献,可能增加
            ......你都是head了贡献不就是自己的长度吗
            */
            ans+=shu[head];
            ans-=a[head].r-a[head].l+1;
            ++head;
        }
        cnt+=head-1;
        pos[i]=head-1;
        if(cnt>K) return K+1;
    }
    return cnt;
}

int main () {
    //freopen("a.in","r",stdin);
    scanf("%d%d",&n,&K);
    for1(1,n,i) a[i].in();
    
    for1(1,n,i) {
    
        f[i]=a[i].r-a[i].l+1;
        while (cc.size()) {
            set <node>::iterator it;
            it=cc.upper_bound((node){a[i].l-1,0,0});
            if(it==cc.end()) break;
            if(it->l>a[i].r) break;
            f[i]-=min(it->r,a[i].r)-it->l+1;
            buc[i].push_back((point){min(it->r,a[i].r)-it->l+1,it->t});
            int l=a[i].r+1,r=it->r,t=it->t;
            cc.erase(it);
            if(l<=r) cc.insert((node){l,r,t});
        }
        
        if(cc.size()&&cc.begin()->l<a[i].l) {
            set <node>::iterator it;
            it=cc.upper_bound((node){a[i].l-1,0,0});
            it--;
            if(it->r>=a[i].l) {
                f[i]-=min(it->r,a[i].r)-a[i].l+1;
                buc[i].push_back((point){min(it->r,a[i].r)-a[i].l+1,it->t});
                int l=it->l,r=it->r,t=it->t;
                cc.erase(it);
                cc.insert((node){l,a[i].l-1,t});
                if(r>a[i].r) cc.insert((node){a[i].r+1,r,t});
            }
        }
        cc.insert((node){a[i].l,a[i].r,i});
    }
    
    int Min,num;
    for(int l=1,r=1e9,mid,cnt;l<=r;) {
        mid=l+r>>1;
        cnt=calc_(mid);
        if(cnt>K) l=mid+1;
        else Min=mid,r=mid-1;
    }
    
    num=calc_(Min);
    
    ll ans=0;
    for1(1,n,i) {
        int size=buc[i].size();
        for1(1,size,j) {
            point t=buc[i][j-1];
            T_add(t.id,-t.x);
        }
        T_add(i,a[i].r-a[i].l+1);
        
        if(pos[i]) {
            ans+=query(1,pos[i]);
            ans+=(query(0,i)-query(0,pos[i]))*pos[i];
        }
    }
    
    printf("%lld
",ans+1ll*(K-num)*(Min-1));
}

E - Little C Loves 3 |||

灵活的对于$fwt$的应用。

对于|&卷积是非常容易构造的。

对于^的构造不是那么容易想。

但是这三种其实都是$dp[i][j]$代表前$i$为满足条件,并且后面的位都一样的权值和。

在从第$i$位转移到$i+1$位时$x$只与$x|(1<<i)$有关,统一转移。发现第一维并没啥用,就省略了。


子集卷积$O(n^2*logn)$太慢了。

这到题利用了$mod 4$的性质。

我们需要做的就是怎么将$bit[i]+bit[j]==bit[i|j]$与$bit[i]+bit[j]>bit[i|j]$区分开。

那我们不妨将$a[i],b[i]$乘上$4^{bit[i]}$。

这样对于$bit[i]+bit[j]>bit[i|j]$的,至少会多乘上一个$4$。

因为我们又是$mod 4$,所以就完美的消除掉了影响。

出题人对于$fwt$的理解非常6。

#include <bits/stdc++.h>
#define for1(a,b,i) for(int i=a;i<=b;++i)
#define FOR2(a,b,i) for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;

#define M 2100005
#define mod 998244353
int n,m;
char s[M];
int bit[M];
ll a[M],b[M];

int main () {
    //freopen("a.in","r",stdin);
    scanf("%d",&n);
    m=(1<<n)-1;
    scanf("%s",s);
    for1(0,m,i) a[i]=s[i]-'0';
    scanf("%s",s);
    for1(0,m,i) b[i]=s[i]-'0';
    for1(1,m,i) bit[i]=bit[i>>1]+(i&1);
    for1(0,m,i) {
        a[i]=(ll)a[i]<<(2*bit[i]);
        b[i]=(ll)b[i]<<(2*bit[i]);
    }
    
    for1(0,n-1,i) for1(0,m,j) 
        if(j>>i&1) a[j]+=a[j^(1<<i)],b[j]+=b[j^(1<<i)];
    for1(0,m,i) a[i]=a[i]*b[i];
    for1(0,n-1,i) for1(0,m,j) 
        if(j>>i&1) a[j]-=a[j^(1<<i)];
    for1(0,m,i) {
        a[i]=(a[i]>>2*bit[i])&3;
        putchar(a[i]+'0');
    }
}
原文地址:https://www.cnblogs.com/asd123www/p/9758521.html