Educational Codeforces Round 93 (Rated for Div. 2) 简要题解

A

根据“两短边之和大于第三边”,可以直接判断\(a_1,a_2,a_n\)的大小关系即可。

int n,a[N];

int main()
{
    int T=read();
    while (T--)
    {
        n=read();
        rep(i,1,n) a[i]=read();
        sort(a+1,a+1+n);
        int x=a[1],y=a[2],z=a[n];
        if (x+y>z) puts("-1");
        else printf("%d %d %d\n",1,2,n);
    }
    return 0;
}

B

取一段0的话会使得两段1连在一起,显然会帮助对面得到更多的1,所以贪心策略就是按照极长连续1的长度从大到小贪心的取。

int n,m,a[N];
char s[N];

int main()
{
    int T=read();
    while (T--)
    {
        m=0;
        scanf("%s",s+1);
        n=strlen(s+1);
        int cnt=0;
        rep(i,1,n)
        {
            if (s[i]=='0')
            {
                if (cnt) a[++m]=cnt;
                cnt=0;
            }
            else cnt++;
        }
        if (cnt) a[++m]=cnt;
        sort(a+1,a+1+m);
        int ans=0;
        for (int i=m;i>=1;i-=2) ans+=a[i];
        printf("%d\n",ans);
    }
    return 0;
}

C

记前缀和为\(S_i\),由题知有\(S_r-S_{l-1}=r-l+1\),移项得\(S_r-r=S_{l-1}-(l-1)\),用一个map记录\(S_i-i\)的值的个数即可。

int n;
map<int,int> mp;
char str[N];

int main()
{
    int T=read();
    while (T--)
    {
        n=read();scanf("%s",str+1);
        mp.clear();mp[0]=1;
        int sum=0;ll ans=0;
        rep(i,1,n)
        {
            sum+=(str[i]-'0');
            ans+=mp[sum-i];
            mp[sum-i]++;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

D

考虑四条边\(a,b,c,d\)满足\(a<b<c<d\),经过一些简单的验证可以得到两个矩形面积和为\(ab+cd\)时最大(即配对的边不会有交叉),于是可以直接dp.

int n1,n2,n3,a[210],b[210],c[210];
long long f[210][210][210];

int main()
{
    n1=read();n2=read();n3=read();
    rep(i,1,n1) a[i]=read();
    rep(i,1,n2) b[i]=read();
    rep(i,1,n3) c[i]=read();
    sort(a+1,a+1+n1);sort(b+1,b+1+n2);sort(c+1,c+1+n3);
    f[0][0][0]=0;
    rep(i,0,n1) rep(j,0,n2) rep(k,0,n3)
    {
        if (i) f[i][j][k]=max(f[i][j][k],f[i-1][j][k]);
        if (j) f[i][j][k]=max(f[i][j][k],f[i][j-1][k]);
        if (k) f[i][j][k]=max(f[i][j][k],f[i][j][k-1]);
        if ((i) && (j)) f[i][j][k]=max(f[i][j][k],f[i-1][j-1][k]+a[i]*b[j]);
        if ((i) && (k)) f[i][j][k]=max(f[i][j][k],f[i-1][j][k-1]+a[i]*c[k]);
        if ((j) && (k)) f[i][j][k]=max(f[i][j][k],f[i][j-1][k-1]+b[j]*c[k]);
    }
    long long ans=f[n1][n2][n3];
    printf("%lld",ans);
    return 0;
}

E

记0类武器有\(n_0\)个,1类武器有\(n_1\)个,我们需要维护出对当前\(n_0+n_1\)个武器中伤害的前\(n_1\)大值以进行加倍,但是要注意如果这些应该被加倍的武器全部是1类的话,就需要拿走一个最小的,同时在剩余的武器中取出一个最大的。使用两个set维护前\(n_1\)大和非前\(n_1\)大,注意到一次修改操作给两个set带来的插入和删除操作是\(O(1)\)个的,于是可以直接维护,具体细节见下。

set<pii> s0,s1;

bool chk()
{
    if ((s0.empty()) || (s1.empty())) return 0;
    pii x=*s0.begin(),y=*s1.rbegin();
    return x.fir<y.fir;
}

int main()
{
    int q=read();
    ll all=0,sum0=0;
    int n10=0,n1=0,n0=0;
    while (q--)
    {
        int tp=read(),d=read(),op=1;
        all+=d;
        pii now=mkp(d,tp);
        if (d<0)
        {
            d=-d;op=-1;now.fir*=-1;
            if (s0.find(now)!=s0.end()) 
            {
                s0.erase(now);sum0-=d;
                if (now.sec) n10--;
            }
            else s1.erase(now);
        }
        else s1.insert(now);
        if (tp) n1+=op;
        else n0+=op;
        //cout << "now " << n1 << " " << (int)s0.size() << " " << all << endl;
        while (n1<(int)s0.size())
        {
            pii x=*s0.begin();s0.erase(x);sum0-=x.fir;
            if (x.sec) n10--;
            s1.insert(x);
        }
        while (n1>(int)s0.size())
        {
            if (s1.empty()) break;
            pii x=*s1.rbegin();s1.erase(x);
            s0.insert(x);sum0+=x.fir;
            if (x.sec) n10++;
        }
        while (chk())
        {
            pii x=*s0.begin(),y=*s1.rbegin();
            //cout << "change " << x.fir << " " << y.fir << endl;
            if (x.sec) n10--;
            s0.erase(x);s1.erase(y);
            sum0-=x.fir;sum0+=y.fir;
            s0.insert(y);s1.insert(x);
        }
        ll nows=sum0;
        if ((n10==n1) && (n1))
        {
            pii x=*s0.begin();nows-=x.fir;
            if (!s1.empty())
            {
                x=*s1.rbegin();nows+=x.fir;
            }
        }
        printf("%lld\n",nows+all);
    }
    return 0;
}

F

有一个显然的贪心是:枚举\(k\),然后从位置1开始、每次找一个最小的,满足能形成连续\(k\)个0/1的位置跳过去。

根据调和级数那套理论,对于每个\(k\)如果我们能在处于任何位置时,能够用\(f(k)\)找到下一步的起点,那么总的时间复杂度是\(O(f(k)\times n\log n)\).

首先可以预处理\(nxt_{i,0/1}\)表示从i开始往后走的极长0/1段的长度。之后有一个比较暴力的思路是对当前的起点每次二分一个终点,check的话就需要考虑这个区间内\(nxt_{i,0/1}\)的最大值是否\(\geq k\),时间复杂度是\(O(n\log^2n)\), 赛时实测会TLE.

但是由于我们枚举的\(k\)是单调递增的,所以可以用并查集维护以当前点为起点时的最近终点是谁。这样的话就需要在对\(k\)做完答案后将\(\max(nxt_{i,0/1})=k\)\(i\)连向下一个位置,于是每次查询终点近乎线性,可以通过。

int n,nxt[N][2],fa[N],a[N];
char s[N];
vi val[N];

int find(int x)
{
    if (fa[x]==x) return x;
    fa[x]=find(fa[x]);
    return fa[x];
}

int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    n=read();
    scanf("%s",s+1);
    per(i,n,1)
    {
        if (s[i]!='1') nxt[i][0]=nxt[i+1][0]+1;
        if (s[i]!='0') nxt[i][1]=nxt[i+1][1]+1;
        a[i]=max(nxt[i][0],nxt[i][1]);
        val[a[i]].pb(i);
    }
    //rep(i,1,n) cout << a[i] << " ";cout << endl;
    rep(i,1,n+1) fa[i]=i;
    rep(k,1,n)
    {   
        int p=1,cnt=0;
        while (p+k-1<=n)
        {
            int nxt=find(p);
            if (nxt<=n)
            {
                p=nxt+k;cnt++;
            }
            else break;
        }
        printf("%d ",cnt);
        int len=val[k].size();
        rep(i,0,len-1)
        {
            int x=val[k][i],y=x+1;
            int fx=find(x),fy=find(y);
            fa[fx]=fy;
        }
        //rep(i,1,n) cout << find(i) << " ";cout << endl;
    }
    return 0;
}

G

写完F,读懂G之后就会做了,但是没啥时间写了,着实自闭。

考虑求是否存在一个周长为\(C\)的矩形,后面可以用调和级数的复杂度求出最大因数。

去掉边长为\(y\)的边后其实就是求一个集合\(S=\{a_i-a_j|0\leq i,j\leq n,a_i>a_j\}\)中的元素,记\(b_i\)表示是否有\(a_j=i\),那么有\(c_k=\sum_{i=0}^{x-k}b_ib_{i+k}\),这是一个经典的卷积形式,可以将\(b\)翻转得到\(c_k=\sum_{i=0}^{x-k}b_ib_{x-(i+k)}'\),由于值域不大直接NTT即可。

int n,x,y,a[N],b[N],r[N],f[N],ans[N];

void ntt(int lim,int *a,int typ)
{
    rep(i,0,lim-1)
        if (i<r[i]) swap(a[i],a[r[i]]);
    for (int mid=1;mid<lim;mid<<=1)
    {
        int gn=qpow(3,(maxd-1)/(mid<<1)),len=(mid<<1);
        if (typ==-1) gn=getinv(gn);
        for (int sta=0;sta<lim;sta+=len)
        {
            int g=1;
            for (int j=0;j<mid;j++,g=mul(g,gn))
            {
                int x=a[sta+j],y=mul(a[sta+j+mid],g);
                a[sta+j]=add(x,y);a[sta+j+mid]=dec(x,y);
            }
        }
    }
    if (typ==-1)
    {
        int invn=getinv(lim);
        rep(i,0,lim-1) a[i]=mul(a[i],invn);
    }
}

int calcr(int len)
{
    int lim=1,cnt=0;
    while (lim<len) {lim<<=1;cnt++;}
    rep(i,0,lim-1) r[i]=((r[i>>1]>>1)|((i&1)<<(cnt-1)));
    return lim;
}

int main()
{
    n=read();x=read();y=read();
    rep(i,0,n)
    {
        int x=read();a[x]=1;
    }
    rep(i,0,x) b[x-i]=a[i];
    int lim=calcr((x+1)<<1);
    ntt(lim,a,1);ntt(lim,b,1);
    rep(i,0,lim-1) a[i]=mul(a[i],b[i]);
    ntt(lim,a,-1);
    rep(i,1,x) 
    {
        f[i]=a[x+i];
        //if (f[i]) cout << i << " ";
    }
    //cout << endl;
    rep(i,0,M) ans[i]=-1;
    rep(i,0,x)
    {
        if (!f[i]) continue;
        int c=(y+i)*2;
        //cout << "now " << c << endl;
        for (int j=c;j<=M;j+=c) ans[j]=max(ans[j],c);
    }
    int q=read();
    while (q--)
    {
        int x=read();
        printf("%d ",ans[x]);
    }
    return 0;
}
posted @ 2020-08-15 18:22  EncodeTalker  阅读(128)  评论(0编辑  收藏  举报