zoukankan      html  css  js  c++  java
  • 2019牛客暑期多校训练营(第二场)

    https://ac.nowcoder.com/acm/contest/882/F
    潘哥的代码才卡过去了,自己写的都卡不过去,估计跟评测机有关。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=30;
    int g[maxn][maxn],n;
    ll ans;
    inline void dfs(int mask,int last,int dep,ll sum)
    {
        if(!dep){
            if(sum>ans) ans=sum;
            return;
        }
        if(n-last-1<dep) return;
        for(int i=last+1;i<n;++i){
            ll tmp=sum;
            for(int j=0;j<n;++j){
                if(mask>>j&1) tmp-=g[i][j];
                else tmp+=g[i][j];
            }
            dfs(mask|(1<<i),i,dep-1,tmp);
        }
        return;
    }
    int main()
    {
        #ifdef local
        freopen("a.txt","r",stdin);
        #endif // local
        scanf("%d",&n);
        n<<=1;
        for(int i=0;i<n;++i){
            for(int j=0;j<n;++j){
                scanf("%d",&g[i][j]);
            }
        }
        ll sum=0;
        for(int i=1;i<n;++i) sum+=g[0][i];
        dfs(1,0,n/2-1,sum);
        printf("%lld
    ",ans);
        return 0;
    }
    

    应该最卡常的思路是折一半,2^14预处理前半部分内部的贡献,再一次预处理后半部分内部的贡献。
    答案当然就是,前半部分选了i个1,后半部分选n-i个1的,那么考虑他们合并在一起的新的影响。
    暴力的话当然是前半部分的每个1对后半部分的每个0匹配一次。然后后半部分的每个1对前半部分的0匹配一次。

    复杂度的话,每种组合实际上都要遍历到,(C_{2n}^n) 少不了了。但是每次合并只和1的数量有关。所以需要预处理的时候把“前半部分的该状态的0对后半部分的某个1的贡献”也搞出来。

    理论上比这个搜索快多的。这个是咖啡鸡的代码的注释版:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    ll s[1 << 14], t[1 << 14], p[14], q[14], ret, ans;
    int c[30][30], n, d[1 << 14];
    
    //枚举t状态
    void dfs(int step, int w, int mask, ll sum) {
        //对后半部分搜索完毕
        if(step == n) {
            ret = max(ret, sum + t[mask]);
            return;
        }
        //选择的人没满,后半部分可以再选一个
        if(w < n)
            //这一次step选1,也就是后半部分的第step位选1,后半部分第step个选1那当然要加step
            dfs(step + 1, w + 1, mask | (1 << step), sum + p[step]);
        //等价于n-step+w>n,也就是剩下的人+已选的人超过需要选的人,那么这一步可以选0
        if(w > step)
            dfs(step + 1, w, mask, sum + q[step]);
    }
    
    int main() {
    #ifdef Yinku
        freopen("Yinku.in", "r", stdin);
    #endif // Yinku
        scanf("%d", &n);
        //d[i]表示i状态有多少个1,用于连接两个集合使用
        d[0] = 0;
        for(int i = 1; i < (1 << n); i++)
            d[i] = d[i / 2] + i % 2;
        for(int i = 0; i < n * 2; i++)
            for(int j = 0; j < n * 2; j++)
                scanf("%d", &c[i][j]);
        //折半枚举
        //s[i]表示前半部分内部的贡献,t[i]表示后半部分内部的贡献
        for(int i = 0; i < (1 << n); i++) {
            for(int j = 0; j < n; j++)
                if(!(i & (1 << j)))
                    for(int k = j + 1; k < n; k++)
                        if(i & (1 << k))
                            s[i] += c[j][k], t[i] += c[j + n][k + n];
            for(int j = 0; j < n; j++)
                if(i & (1 << j))
                    for(int k = j + 1; k < n; k++)
                        if(!(i & (1 << k)))
                            s[i] += c[j][k], t[i] += c[j + n][k + n];
        }
        //折半枚举
        //p[k]表示当前前半部分为i状态,后半部分的第k个为1的时候的对前半部分的所有的0产生的贡献
        //q[k]表示当前前半部分为i状态,后半部分的第k个为0的时候的对前半部分的所有的1产生的贡献
        for(int i = 0; i < (1 << n); i++) {
            for(int k = 0; k < n; k++)
                p[k] = 0, q[k] = 0;
            for(int j = 0; j < n; j++) {
                if(!(i & (1 << j)))
                    for(int k = 0; k < n; k++)
                        p[k] += c[j][k + n];
                else
                    for(int k = 0; k < n; k++)
                        q[k] += c[j][k + n];
            }
            ret = 0;
            //前半部分为i状态,它拥有d[i]个已选择的人
            dfs(0, d[i], 0, 0);
            //把前半部分的s[i]状态加进去
            ans = max(ans, ret + s[i]);
        }
        printf("%lld
    ", ans);
    }
    
  • 相关阅读:
    servlet里面拿到common.property的属性
    js 播放声音文件
    dataGridViewX操作
    CYQ学习主要摘要4
    CYQ学习主要摘要3
    CYQ学习主要摘要2
    CYQ学习主要摘要
    EF操作VS中
    C# 文件与二进制互转数据库写入读出
    简单的线程与界面通用方法,不是很好,但是很方便
  • 原文地址:https://www.cnblogs.com/Yinku/p/11223495.html
Copyright © 2011-2022 走看看