zoukankan      html  css  js  c++  java
  • 18.8.23 考试总结

    今天的考试我是真的...emmmm

    考试的时候我乱搞 然后狗了三十分...

    一些大佬用状压dp弄得 然后还有另外一些大佬是贪心枚举所有情况 正解是非常简短的

    这道题可以总结出一个规律 当m ≤ 5的时候 可以推出如果两行的数&起来是 0 那么就一定合法

    证明: 因为m ≤ 5    又因为做过的题目小于等于没做过的 所以说0的个数一定大于等于1的个数

    那么0最多的一行肯定至少有ceil(m / 2)个0 否则0就不够多 

    那么该行就有至多两个1  如果剩下的方案中没有一种方案在那两个1对应的两列全为0

    那么该列0的个数就永远不可能大于等于1 就不合法

    因为如果只有一列为0 另一列是1 那么那两列1的个数永远大于0的个数 就不可能合法

    所以只需要找到两个&起来为0的就可以了 只有一个1的同理 

    代码

    #include <iostream>
    #include <stdio.h>
    #include <cstring>
    using namespace std;
    
    const int N = 1000;
    int T,n,vis[N],x,m;
    
    inline int read( ) {
        
        int ans = 0,t = 1;
        char x; x = getchar( );
        while(x < '0' || x > '9') {
            if(x == '-') t = -1;
            x = getchar( );
        }
        while(x >= '0' && x <= '9') {
            ans = ans * 10 + x - '0';
            x = getchar( );
        }
        return ans * t;
    }
    
    int main( ) {
        
        freopen("prob.in","r",stdin);
        freopen("prob.out","w",stdout);
        T = read( );
        while(T --) {
            memset(vis,0,sizeof(vis));
            n = read( ); m = read( );
            for(int i = 1;i <= n;i ++) {
                int v = 0;
                for(int j = 1;j <= m;j ++) {
                    x = read( );
                    v <<= 1; v |= x;
                } 
                vis[v] = true;
            }
            for(int i = 0;i < (1 << m);i ++)
              for(int j = 0;j < (1 << m);j ++)
                if(!(i & j) && vis[i] && vis[j]) {
                    printf("YES
    ");
                    goto Next;
                }
            printf("NO
    ");
            Next:;
        }
    }

    这道题就是一道贪心...然后细节有点多

    我们考虑把所有客人分为两组 a ≥ b的分为第一组 a < b 的分为第二组

    然后再对两组分别排序 如果是第一组 则按照a - b的大小从大到小排序

    另外一组则是b - a的大小从大到小排序

    对于第一组而言 a - b的大小越大 那么也就是说如果让这个客人去吃第二组的饼饼 他亏得高兴度就会更多

    所以我们想要让亏掉的高兴度最少 那么就优先满足a - b大的 第二组同理

    那么现在问题就是如何分配饼饼 因为总饼数是一定的

    所以我可能是第一组全部吃到喜欢的饼饼第二组的饼饼不够吃  然后第二组的人最后那几个就去吃第一组的饼饼

    要不就是第一组的不够吃 最后几个人去吃第二组的 所以就乱搞搞判断就可以了

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int N = 1e5 + 5;
    ll sum = 0,ans,sum1,sum2,S;
    int n,m,num1 = 0,num2 = 0;
    struct gues {
        
        ll s;
        int a,b;
    }g1[N],g2[N];
    
    inline int read( ) {
        
        int ans = 0,t = 1;
        char x; x = getchar( );
        while(x < '0' || x > '9') {
            if(x == '-') t = -1;
            x = getchar( );
        }
        while(x >= '0' && x <= '9') {
            ans = ans * 10 + x - '0';
            x = getchar( );
        }
        return ans * t;
    }
    
    bool cmp1(const gues & q,const gues & p) {
        
        return q.a - q.b > p.a - p.b;
    }
    
    bool cmp2(const gues & q,const gues & p) {
        
        return q.b - q.a > p.b - p.a;
    }
    
    void solve(ll s1,ll s2) {
        
        ll aa = s1 * S,bb = s2 * S;
        ll cmp = 0,rem = g1[1].s;
        int i = 1; 
        for(i = 1;i <= num1 && aa > 0;i ++) {
            ll del = min(g1[i].s,aa);
            cmp += (1LL * g1[i].a * del);
            aa -= del;
            rem = g1[i].s - del;
        }
    
        if(i > 1) i --;
        if(s1 * S < sum1) {
            cmp += rem * 1LL * g1[i].b; i ++;
            bb -= rem;
            for(;i <= num1;i ++) {
                cmp += (1LL * g1[i].b * g1[i].s);
                bb -= g1[i].s;
            }
        }
        int j = 1;
        rem = g2[1].s;
        for(j = 1;j <= num2 && bb > 0;j ++) {
            ll del = min(g2[j].s,bb);
            cmp += (1LL * g2[j].b * del);
            bb -= del;
            rem = g2[j].s - del;
        }
        if(j > 1) j --;
        if(s2 * S < sum2) {
            cmp += rem * 1LL * g2[j].a; j ++;
            aa -= rem;
            for(;j <= num2;j ++) {
                cmp += (1LL * g2[j].a * g2[j].s);
                aa -= g2[j].s;
            }
        }
        ans = max(ans,cmp);
    }
    
    int main( ) {
        
        freopen("pizza.in","r",stdin);
        freopen("pizza.out","w",stdout);
        scanf("%d%I64d",& n,& S);
        for(int i = 1;i <= n;i ++) {
            int s,a,b;
            s = read( ); a = read( ); b = read( );
            gues w; w.s = (ll)s; w.a = a,w.b = b;
            if(a >= b) {
                g1[++ num1] = w;
                sum1 += (ll)s;
            }
            else {
                g2[++ num2] = w;    
                sum2 += (ll)s;
            }
            sum += (ll)s;
        }
        
        m = (sum % S == 0) ? 1LL * sum / S : 1LL * sum / S + 1;
        sort(g1 + 1,g1 + num1 + 1,cmp1);
        sort(g2 + 1,g2 + num2 + 1,cmp2);
        solve(sum1 / S,m - sum1 / S);
        if(m - sum1 / S - 1 >= 0)
           solve(sum1 / S + 1,m - sum1 / S - 1);
        if(sum1 / S  - 1 >= 0)
           solve(sum1 / S  - 1,m - sum1 / S + 1);
        printf("%I64d",ans);
    }

    这道题可以说是真够鹅星了...呕

    是一道线段树优化dp的问题 dp方程是显而易见的 

    dp[ i ][ j ]表示我选择i个冰淇淋 用了j个桶桶所能获得的最大收益

    那么转移是和柠檬那道题 诗人小G很像的 dp[ i ][ j ] = dp[ k ][ j - 1 ] + c[ k + 1 ][ i ]

    c[ k + 1 ][ i ]表示k + 1到 i 这一段的贡献 也就是有是多少种不同的冰淇淋

    朴素的转移是枚举 i j k 然后暴力跑出来 k + 1 到 i 的贡献 但是这复杂度就是O(m * n2)

    非常不优秀 然后我们可以发现dp只和上一层j - 1有关 就可以滚一滚

    然后发现dp[ i ][ j ]是在1 ~ i - 1里面去一个max 所以考虑用线段树维护区间最大值

    但是由于在dp的时候随着i的变化 c也是随着变化的 所以还要考虑怎么维护c

    对于位置i 他比i - 1多了a[ i ]这一种颜色 但是a[ i ]只会在和他一样的冰淇淋上一次出现的位置 + 1 ~ i产生贡献

    所以每次就只用在他上一次出现的位置  + 1 ~ i加上1就可以了 然后查询区间max

    (这份代码线段树维护的是dp[ k ][ j ] + c[ k + 1 ][ i ]) 所以每次修改是从prev[i] 开始改到i - 1 我当时理解了好久...

    代码

    #include <bits/stdc++.h>
    #define oo 1e7
    using namespace std;
    
    const int N = 2 * 1e5 + 5;
    int n,m,tag[4 * N],f[4 * N],dp[N][2],a[N];
    int prev[N],las[N],now;
    
    void push_down(int o) {
        
        if(tag[o]) {
            f[2 * o] += tag[o];
            f[2 * o + 1] += tag[o];
            tag[2 * o] += tag[o];
            tag[2 * o + 1] += tag[o];
            tag[o] = 0;
            return ;
        }
    }
    
    void update(int o) {
        
        f[o] = max(f[2 * o],f[2 * o + 1]);
    }
    
    void modify(int o,int l,int r,int L,int R,int del) {
        
        if(l >= L && r <= R) {
            f[o] += del;
            tag[o] += del;
            return ;
        }
        push_down(o);
        int mid = (l + r) >> 1;
        if(L <= mid) modify(2 * o,l,mid,L,R,del);
        if(mid < R)  modify(2 * o + 1,mid + 1,r,L,R,del);
        update(o);
    }
    
    int query(int o,int l,int r,int L,int R) {
        
        if(l >= L && r <= R) {
            return f[o];
        }
        push_down(o);
        int mid = (l + r) >> 1;
        int ans = 0;
        if(L <= mid) ans = max(ans,query(2 * o,l,mid,L,R));
        if(mid < R)  ans = max(ans,query(2 * o + 1,mid + 1,r,L,R));
        return ans;
    }
    
    void build(int o,int l,int r) {
        
        tag[o] = 0;
        if(l == r) {
            f[o] = dp[l][now ^ 1];
            return ;
        }
        int mid = (l + r) >> 1;
        build(2 * o,l,mid);
        build(2 * o + 1,mid + 1,r);
        update(o);
    }
    
    int main( ) {
        
        freopen("scream.in","r",stdin);
        freopen("scream.out","w",stdout);
        scanf("%d%d",& n,& m);
        for(int i = 1;i <= n;i ++) {
            scanf("%d",& a[i]);
            prev[i] = las[a[i]];
            las[a[i]] = i;
        }
        now = 0;
        for(int i = 1;i <= n;i ++) dp[i][now] = dp[i - 1][now] + (prev[i] == 0);
        for(int j = 2;j <= m;j ++) {
            now ^= 1;
            build(1,1,n);
            for(int i = 1;i <= n;i ++) {
                if(i < j) dp[i][now] = -oo;
                else {
                    int pos = max(1,prev[i]);
                    modify(1,1,n,pos,i - 1,1);
                    dp[i][now] = query(1,1,n,1,i - 1);
                }
            }
        }
        printf("%d",dp[n][now]);
    }
  • 相关阅读:
    问题集
    第04次作业-树
    06-图
    05-查找
    04-树
    03-栈和队列
    02-线性表
    01-抽象数据类型
    C语言--总结报告
    C语言--函数嵌套
  • 原文地址:https://www.cnblogs.com/Rubenisveryhandsome/p/9525110.html
Copyright © 2011-2022 走看看