• cf 1556(div1+div2)


    比赛链接
    半天才做出B,更半天才做出C——因为少考虑了一种情况,C还WA了两次,最后才过的……

    A

    分析:

    相当于两边同时(+x),然后一边(+k)、一边(-k)。稍微判断一下就好了。

    代码如下
    
    #include<iostream>
    using namespace std;
    int T,c,d;
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&c,&d);
            if((c+d)&1){puts("-1"); continue;}
            int x=(c+d)/2,ans=0;
            if(x)ans++;
            if(c==x&&d==x){printf("%d
    ",ans); continue;}
            if(x-c==d-x)ans++; else ans+=2;
            printf("%d
    ",ans);
        }
        return 0;
    }
    
    

    B

    分析:

    首先,最终状态一种是奇数位置是奇数、偶数位置是偶数;一种是奇数位置是偶数、偶数位置是奇数。用这个判断一下不合法情况就行了。然后两种取优。
    至于怎么挪……一开始想了半天(set)、链表之类,为了维护挪了以后的序列。但是后来发现不用,因为每个数最终在的位置是确定的,比如奇数就是按它们的相对顺序排列在奇数/偶数位置上(保持相对顺序使操作次数最少)。所以按顺序直接朝目标位置挪就行。
    那么怎么考虑奇数和偶数挪的时候彼此的影响呢?实际上只考虑把奇数挪到合适位置上就行,挪完以后会发现偶数也自然在合适位置上了。
    我们从前往后按顺序挪,那会不会出现前面的一个奇数要挪到后面去,挪的过程中把后面的那个奇数挪前了,导致答案算错而且不优?实际上也不会。因为这种可以看作是先挪了后面的、再挪前面的(因为它们都要挪到后面去,所以这样更优)。

    代码如下
    #include<iostream>
    #define ll long long
    using namespace std;
    int const N=1e5+5;
    ll const inf=1e12;
    int T,n,a[N];
    int ab(int x){return x<0?-x:x;}
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&n); int num0=0,num1=0;
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&a[i]); a[i]%=2;
                if(!a[i])num0++; else num1++;
            }
            if(n&1)
            {
                if(ab(num0-num1)!=1){puts("-1"); continue;}
                ll ans=0;
                if(num0>num1)
                {
                    for(int i=1,p0=1,p1=2;i<=n;i++)
                    {
                        if(!a[i])ans+=ab(i-p0),p0+=2;
                        // else ans+=(i-p1),p1+=2;
                    }
                }
                else
                {
                    for(int i=1,p1=1,p0=2;i<=n;i++)
                    {
                        if(!a[i])ans+=ab(i-p0),p0+=2;
                        // else ans+=(i-p1),p1+=2;
                    }
                }
                printf("%lld
    ",ans);
            }
            else
            {
                if(num0!=num1){puts("-1"); continue;}
                ll a1=0,a2=0;
                for(int i=1,p0=1,p1=2;i<=n;i++)
                {
                    if(!a[i])a1+=ab(i-p0),p0+=2;
                    // else a1+=(i-p1),p1+=2;
                }
                for(int i=1,p1=1,p0=2;i<=n;i++)
                {
                    if(!a[i])a2+=ab(i-p0),p0+=2;
                    // else a2+=(i-p1),p1+=2;
                }
                printf("%lld
    ",min(a1,a2));
            }
        }
        return 0;
    }
    

    C

    分析:

    这个也想了好久……我们两个两个枚举,也就是当前(c_i)是一群左括号,(c_{i+1})是一群右括号。然后我们用一个栈记录两个值:(rem[i])(pre[i]),分别表示前面剩下的左括号,以及那个左括号后面跟了多少个合法的最小括号序列。下面我们考虑每个合法子串的右端点。
    (c_i>c_{i+1}),说明当前左括号多于右括号,那么(ans += c_{i+1}),而(c_{i+1})这些右括号作为右端点也再不能往前走了。栈增加一个元素,(res[i] = c_i - c_{i+1}, pre[i]=1)
    (c_i<c_{i+1}),说明当前右括号多于左括号,那么首先$ans += c_i, c_{i+1} -= c_i $ ,然后右括号继续往前走,也就是提取栈里的元素,每次 $ ans += pre[i], ans += rem[i], c_{i+1} -= rem[i] $ ,直到栈空了或者 $ c_{i+1} leq rem[i] $ 。
    如果是 $ c_{i+1} < rem[i] $ ,说明右括号在这里用完了,那么 $ ans += c_{i+1}, rem[i] -= c_{i+1} $ ,然后 $ pre[i]=1 $ ,表示当前最右的括号与栈内此元素的左括号形成了一个合法子串。
    如果是 $ c_{i+1} = rem[i] $ ,那么左右括号在这里恰好匹配完了。对应操作一番即可。这里要注意!!匹配完以后 $ ans $ 还要加上栈内前一个元素的 $ pre $ ,表示匹配后的子串还可以连上那些合法子串计入答案!!一开始写的时候没注意这个,调了一小时。
    若 $ c_i=c_{i+1} $ ,和上面类似。
    因为每个位置至多入栈出栈一次,所以时间复杂度是 $ O(n) $ 的。

    代码如下
    #include<iostream>
    #define ll long long
    using namespace std;
    int const N=1005;
    int n,c[N],cnt;
    ll ans,rem[N],pre[N];
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&c[i]);
        for(int i=1;i<=n;i+=2)
        {
            if(i==n)continue;//!
            if(c[i]>c[i+1])
            {
                ans+=c[i+1];
                rem[++cnt]=c[i]-c[i+1]; pre[cnt]=1;
            }
            else if(c[i]==c[i+1])
            {
                ans+=c[i];
                if(cnt)ans+=pre[cnt],pre[cnt]++;//rem[cnt]=0 only at head
                else rem[++cnt]=0,pre[cnt]=1;
            }
            else
            {
                ans+=c[i]; int rt=c[i+1]-c[i];
                while(cnt&&rt>rem[cnt])//rt!=0
                {
                    ans+=pre[cnt]; ans+=rem[cnt];
                    rt-=rem[cnt]; cnt--;
                }
                if(cnt)
                {
                    /*
                    if(rt==0)//! rt=0&&rem=0
                    {
                        ans+=pre[cnt]; pre[cnt]++;
                        continue;
                    }
                    */
                    if(rt==rem[cnt])
                    {
                        ans+=pre[cnt]; ans+=rem[cnt];
                        cnt--;
                        if(cnt)
                        {
                            ans+=pre[cnt];//!!
                            pre[cnt]++;
                        }
                        else rem[++cnt]=0,pre[cnt]=1;
                    }
                    else
                    {
                        ans+=pre[cnt]; ans+=rt;
                        rem[cnt]-=rt; pre[cnt]=1;
                    }
                }
            }
            // printf("i=%d ans=%lld
    ",i,ans);
        }
        printf("%lld
    ",ans);
        return 0;
    }
    

    D

    分析:

    又忘记了那个重要的式子: $ (x | y) + (x $ & $ y) = (x+y) $
    所以可以问六次得到前三个数的值;知道一个数的值以后就可以 $ 2n $ 次询问把后面 $ n $ 个数都得到。然后排序即可。
    如此简单粗暴……

    代码如下
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int const N=1e4+5;
    int n,k,a[N];
    int main()
    {
        scanf("%d%d",&n,&k);
        ll s1,s2,s3,sum,x,y;
        puts("or 1 2"); fflush(stdout);
        scanf("%lld",&x);
        puts("and 1 2"); fflush(stdout);
        scanf("%lld",&y);
        s1=x+y;
        puts("or 2 3"); fflush(stdout);
        scanf("%lld",&x);
        puts("and 2 3"); fflush(stdout);
        scanf("%lld",&y);
        s2=x+y;
        puts("or 1 3"); fflush(stdout);
        scanf("%lld",&x);
        puts("and 1 3"); fflush(stdout);
        scanf("%lld",&y);
        s3=x+y;
        sum=(s1+s2+s3)/2;
        a[1]=sum-s2; a[2]=sum-s3; a[3]=sum-s1;
        for(int i=4;i<=n;i++)
        {
            printf("or 1 %d
    ",i); fflush(stdout);
            scanf("%lld",&x);
            printf("and 1 %d
    ",i); fflush(stdout);
            scanf("%lld",&y);
            a[i]=x+y-a[1];
        }
        sort(a+1,a+n+1);
        printf("finish %d
    ",a[k]);
        return 0;
    }
    

    E

    分析:

    巧妙地把这个序列做两个转换:
    首先,令 $ c_i = a_i - b_i $
    然后,对 $ c_i $ 做前缀和 $ s_i $
    于是问题就转化成使 $ s_l $ 到 $ s_r $ 都相等,每次操作可以把一些子段整体 $ +1 $ 。所以去掉不合法情况后,直接输出 $ s_{l-1} - min ( s_l, ..., s_r ) $ 即可!

    代码如下
    #include<iostream>
    #include<cmath>
    #define ll long long
    using namespace std;
    int const N=1e5+5;
    int n,q;
    ll a[N],s[N],mx[N][20],mn[N][20];
    int rd()
    {
        int ret=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
        while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
        return ret*f;
    }
    ll getmn(int l,int r)
    {
        int k=log2(r-l+1);
        return min(mn[l][k],mn[r-(1<<k)+1][k]);
    }
    ll getmx(int l,int r)
    {
        int k=log2(r-l+1);
        return max(mx[l][k],mx[r-(1<<k)+1][k]);
    }
    int main()
    {
        n=rd(); q=rd();
        for(int i=1;i<=n;i++)a[i]+=rd();
        for(int i=1;i<=n;i++)a[i]-=rd();
        for(int i=1;i<=n;i++)
        {
            s[i]=s[i-1]+a[i];
            mx[i][0]=mn[i][0]=s[i];
        }
        for(int j=1;j<20;j++)
            for(int i=1;i<=n&&i+(1<<j)-1<=n;i++)
                mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]),
                mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
        for(int i=1,l,r;i<=q;i++)
        {
            l=rd(); r=rd();
            if(s[r]!=s[l-1]||getmx(l,r)>s[l-1]){puts("-1"); continue;}
            printf("%lld
    ",s[l-1]-getmn(l,r));
        }
        return 0;
    }
    

    F

    分析:

    容斥!又是美丽的容斥。
    设 $ F[X] $ 表示集合 $ X $ 为胜利者。 $ ans = sum F[X] * |X| $
    设 $ G[X] $ 表示集合 $ X $ 的子集为胜利者。 $ G[X] = g(X,U-X) $ ,其中 $ U $ 是全集, $ g(A,B) $ 表示 $ A $ 中所有人都直接赢了 $ B $ 中所有人,即 $ g(A,B) = prod_{x in X, y in Y} frac{a_x}{a_x+a_y} $ 。可以想到,如果 $ A $ 中所有人都赢了 $ B $ 中所有人,那么 $ B $ 中的人无论怎样也无法赢回 $ A $ ,所以胜利者一定在 $ A $ 之中。
    然后, $ F[X] = G[x] - sum_{T subseteq X} F[T] * g(X-T,U-X) $ 。减去的是在此种情况下, $ X $ 中只有一部分人( $ T $ )是真正的胜利者的概率;“此种情况”即 $ X $ 所有人都赢了 $ U-X $ 所有人,所以对于一个要减去的 $ F[T] $ 来说,它还乘了 $ X-T $ 那部分人也赢了 $ U-X $ 的概率。
    还专门想了想 $ F[0] $ 为什么等于 $ 0 $ (虽然没用到)。也就是在这样一个有向完全图中,一定会有点能够走到所有点。可以这样考虑:如果存在一个点能走到 $ n-1 $ 个点,那么它走不到的那个点一定能直接走到它(因为是完全图),进而从它走到其他点;也就是那个点能够走到所有点。如果存在一个点能走到 $ n-2 $ 个点,那么它走不到的两个点一定能直接走到它,也就是它们能走到 $ n-1 $ 个点,再根据之前的讨论,可知存在能走到所有点的点。以此类推,总是会有点至少能走到一个点的,所以总是会有点能够走到所有点。
    所以cf的Tutorial公式果然写得有点问题?直接那样写了的我调了好久,才自己写出正确的公式……(Tutorial后来改了……)
    计算 $ G[X] $ 可以预处理每个人 $ i $ 对各种集合 $ S $ 的胜率,记为 $ H[i] = prod_{j in S} frac{a_i}{a_i+a_j} $ ;之后求 $ g(X,Y) $ 就可以 $ O(n) $ 枚举 $ X $ 中的元素得到了。Tutorial还进一步给出了 $ O(1) $ 算 $ g(X,Y) $ 的方法,似乎是把一个集合裂成两半……感觉有点麻烦,再者原来那样时间也可以,就没优化了。
    时间复杂度 $ O(3^n * n) $ , 其中 $ 3^n $ 是枚举每个数在 $ X $ 中的 $ T $ ,在 $ X $ 中而不在 $ T $ ,还是不在 $ X $ ; $ n $ 是每次计算 $ g(X,Y) $ 的时间。

    代码如下
    #include<iostream>
    #include<cstring>
    #define ll long long
    using namespace std;
    int const N=15,M=(1<<15),md=1e9+7;
    int n,a[N],U;
    ll ans,H[N][M],F[M];
    ll pw(ll x,ll y)
    {
        ll ret=1,nx=x;
        while(y)
        {
            if(y&1)ret=ret*nx%md;
            nx=nx*nx%md;
            y>>=1;
        }
        return ret;
    }
    ll fr(ll x,ll y){return x*pw(y,md-2)%md;}
    void init()
    {
        for(int i=1;i<=n;i++)
            for(int s=0;s<=U;s++)
            {
                H[i][s]=1;
                for(int j=1;j<=n;j++)
                    if(s&(1<<(j-1)))H[i][s]=H[i][s]*fr(a[i],a[i]+a[j])%md;
                // printf("H[%d][%d]=%lld
    ",i,s,H[i][s]);
            }
    }
    ll getg(int X,int Y)
    {
        ll ret=1;
        for(int i=1;i<=n;i++)
            if(X&(1<<(i-1)))ret=ret*H[i][Y]%md;
        // printf("g(%d,%d)=%lld
    ",X,Y,ret);
        return ret;
    }
    ll cal(int X)
    {
        if(F[X]!=-1)return F[X];
        ll ml=0;
        for(int T=X;T;T=(T-1)&X)
            if(T!=X)ml=(ml+cal(T)*getg(X-T,U-X)%md)%md;
            // if(T!=X)ml=(ml+cal(T)*getg(T,X-T)%md)%md;//+
        // printf("ml=%lld F[%d]=%lld
    ",ml,X,(getg(X,U-X)*(1-ml)%md+md)%md);
        // return F[X]=(getg(X,U-X)*(1-ml)%md+md)%md;
        // printf("ml=%lld F[%d]=%lld
    ",ml,X,((getg(X,U-X)-ml)%md+md)%md);
        return F[X]=((getg(X,U-X)-ml)%md+md)%md;
    }
    void dfs(int nw,int X,int cnt)
    {
        if(nw>n)
        {
            // printf("nw=%d X=%d cnt=%d f=%lld
    ",nw,X,cnt,F[X]);
            ans=(ans+(ll)cnt*F[X]%md)%md;
            return;
        }
        dfs(nw+1,X|(1<<(nw-1)),cnt+1);
        dfs(nw+1,X,cnt);
    }
    int main()
    {
        scanf("%d",&n); U=(1<<n)-1;
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        init();
        memset(F,-1,sizeof F); F[0]=0; cal(U);
        dfs(1,0,0);
        printf("%lld
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    Java垃圾回收
    Android Starting Window(Preview Window)
    JVM虚拟机结构
    表驱动法 -《代码大全》读书笔记
    快速Android开发系列网络篇之Retrofit
    快速Android开发系列网络篇之Volley
    快速Android开发系列网络篇之Android-Async-Http
    清除Android工程中没用到的资源
    快速Android开发系列通信篇之EventBus
    Android点击列表后弹出输入框,所点击项自动滚动到输入框上方
  • 原文地址:https://www.cnblogs.com/Zinn/p/15205098.html
Copyright © 2020-2023  润新知