2018"百度之星"程序设计大赛 - 资格赛 1001状压 1002 前缀和 1003 BFS寻路的KM算法 1005 dp+树状数组 1006最小生成树

21 篇文章 0 订阅
19 篇文章 0 订阅

1001
题意:给n份问卷,每个问卷m道题,每题只有A,B两种选项,问存在多少个问题集合,使得只保留这些问题后至少k对卷子不同。
思路:状压,最大只有(1<<10),用二进制表示选择了哪些题,转为数字,统计这个数字出现的次数 num , num * ( n - num ) 就是这个集合对 不同卷子对数 贡献的2倍,除以2(去重),跟k比较。
Code:

#include <bits/stdc++.h>
using namespace std;
const int AX = 1024;
int a[AX];
char s[AX][20];
int b[AX][20];
unordered_map<int,int>mp;
int main(){
    int T;
    scanf("%d",&T);
    int n , m , k ;
    int Case = 0 ;
    while( T-- ){
        int res = 0 ;
        scanf("%d%d%d",&n,&m,&k);
        for( int i = 0 ; i < n ;  i++ ){
            scanf("%s",s[i]);
        }
        int MAX = ( 1 << m );
        for( int i = 1 ; i < MAX ; i++ ){
            int tot = 0 ;
            for( int j = 0 ; j < m ; j++ ){
                if( i & ( 1 << j ) ){
                    for( int kk = 0 ; kk < n ; kk++ ){
                        b[kk][tot] = s[kk][j] - 'A';
                    }
                    tot ++;
                }
            }
            for( int j = 0 ; j < n ; j++ ){
                a[j] = 0 ;
                for( int kk = 0 ; kk < tot ; kk++ ){
                    a[j] <<= 1 ;
                    a[j] |= b[j][kk];
                }
            }
            mp.clear();
            for( int kk = 0 ; kk < n ; kk++ ){
                mp[a[kk]] ++;
            }
            int tmp = 0; 
            for( auto kk : mp ){
                tmp += ( n - kk.second ) * kk.second;
            }
            if( tmp / 2 >= k ) res ++;
        }
        printf("Case #%d: %d\n", ++Case, res);
    }
    return 0 ; 
}

1002
思路:前缀和维护即可。
Code:

#include <bits/stdc++.h>
using namespace std;
const int AX = 1e5+66;
int sum[AX][30];
char s[AX];
int main(){
    int T;
    scanf("%d",&T);
    int Case = 0; 
    while( T-- ){
        memset( sum , 0 , sizeof(sum) );
        int n , q ;
        scanf("%d%d",&n,&q);
        scanf("%s",s+1);
        int l , r ;
        for( int i = 1 ; i <= n ; i++ ){
            int id = s[i] - 'A';
            for( int j = 0 ; j < 26 ; j++ ){
                if( id == j ) sum[i][id] = sum[i-1][id] + 1 ;
                else sum[i][j] = sum[i-1][j] ; 
            }
        }
        printf("Case #%d:\n",++Case);
        while( q -- ){
            scanf("%d%d",&l,&r);
            for( int i = 0 ; i < 26 ; i++ ){
                if( sum[r][i] - sum[l-1][i] ){
                    printf("%d\n",sum[r][i] - sum[l-1][i]);
                    break;
                }
            }
        }
    }
    return 0 ;
}

1003
题意:看作是二分图,那么本题就是求顶标和的最大值,也就是最小权匹配
思路:bfs实现的KM算法求最小权匹配,先取负值,求出最大权匹配后,取反即可。
Code:

#include <bits/stdc++.h>
#pragma comment(linker, “/STACK:1024000000,1024000000”)
#define INF 1e9
#define LL long long
using namespace std;
const int AX = 3e2+6;
LL w[AX][AX];
LL lx[AX] , ly[AX];
int linker[AX];
LL slack[AX];
int n ;
bool visy[AX];
int pre[AX];
void bfs( int k ){
    int x , y = 0 , yy = 0 , delta;
    memset( pre , 0 , sizeof(pre) );
    for( int i = 1 ; i <= n ; i++ ) slack[i] = INF;
    linker[y] = k;
    while(1){
        x = linker[y]; delta = INF; visy[y] = true;
        for( int i = 1 ; i <= n ;i++ ){
            if( !visy[i] ){
                if( slack[i] > lx[x] + ly[i] - w[x][i] ){
                    slack[i] = lx[x] + ly[i] - w[x][i];
                    pre[i] = y; 
                }
                if( slack[i] < delta ) delta = slack[i] , yy = i ;
            }
        }
        for( int i = 0 ; i <= n ; i++ ){
            if( visy[i] ) lx[linker[i]] -= delta , ly[i] += delta;
            else slack[i] -= delta;
        }
        y = yy ;
        if( linker[y] == -1 ) break;
    }
    while( y ) linker[y] = linker[pre[y]] , y = pre[y];
}

void KM(){
    memset( lx , 0 ,sizeof(lx) );
    memset( ly , 0 ,sizeof(ly) );
    memset( linker , -1, sizeof(linker) );
    for( int i = 1 ; i <= n ; i++ ){
        memset( visy , false , sizeof(visy) );
        bfs(i);
    }
}

int main(){
    int T;
    scanf("%d",&T);
    int Case = 0 ;
    LL x ;
    while( T-- ){
        scanf("%d",&n);
        for( int i = 1 ; i <= n ; i++ ){
            for( int j = 1 ; j <= n ; j++ ){
                scanf("%lld",&x);
                w[i][j] = 0 - x ;
            }
        }
        KM();
        LL res = 0 ;
        for( int i = 1 ; i <= n ; i++ ){
            if( linker[i] != -1 ){
                res += w[linker[i]][i] ;
            }
        }
        printf("Case #%d: %lld\n",++Case,0-res);
    }
    return 0 ;
}

1005
题意:1-n的随机排列,问长度为k的上升子序列数分别为多少。
思路:首先会容易想到一个O(n^3) 的dp:
dp[ i ][ j ] = Σ ( 1 <= k <= j ) dp[i-1] [ k ] ( a[k] < a[j] )
dp[i][j] 表示以a[j]结尾长度为i的上升序列个数。

但是题目的n是1e4数量级的,会T.
然后我想用二维树状数组对于每个j建立一个树状数组,维护dp[i][j]前缀和又MLE了。
请教了别人后发现可以在加一个滚动数组减少树状数组维护的工作量,
最后用一维树状数组维护每次长度以a[j]结尾的前缀和,然后加个dp[][j]滚动数组记录以a[j]结尾的长度 i 的序列个数.
复杂度就转化成n*n*logn(其实应该还好点,听大佬说是sqrt(n)*n*logn)。
我多写个memset就TLE了。。以后不该写的代码不要写。。
Code:

#include <bits/stdc++.h>
#pragma comment(linker, “/STACK:1024000000,1024000000”)
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
const int AX = 1e4;
const int MOD = 1e9+7;
int a[AX];
int c[AX];
int dp[2][AX];
int n ;
int lowbit(int x){
    return x&(-x);
}
void update( int value , int site ){
    while( site <= n ){
        c[site] = ( 0LL + c[site] + value ) % MOD;
        site += lowbit(site);
    }
}
int getsum(int i){
    int sum = 0;
    while( i >= 1 ){
        sum = ( 0LL + sum + c[i] ) % MOD ;
        i -= lowbit(i);
    }
    return sum ;
}

int main(){
    int T;
    scanf("%d",&T);
    int Case = 0 ;
    while( T-- ){
        memset( dp, 0 , sizeof(dp) );
        scanf("%d",&n);
        int cur = 0 ;
        for( int i = 1 ; i <= n ; i ++ ){
            scanf("%d",&a[i]); 
            dp[cur][i] = 1 ;
        }
        printf("Case #%d: ",++Case);
        printf("%d",n);
        int f = 1 ;
        for( int i = 2 ; i <= n ; i++ ){
            cur = !cur;
            LL ans = 0LL ;
            if( f ){
                memset( c , 0 , sizeof(c) );
                for( int j = 1 ; j <= n ; j ++ ){
                    dp[cur][j] = getsum( a[j] - 1 ) % MOD;
                    ans = ( ans + dp[cur][j] ) % MOD;
                    update( dp[!cur][j] , a[j] );
                }
            }
            if( !ans ) f = 0; 
            printf(" %d",ans%MOD);
        }
        printf("\n");
    }
    return 0 ;
}

1006

思路:分别求红绿和蓝绿的最短路,然后前n-2条边肯定是不能组成n个点的最小生成树,所以都输出-1,然后分别求出每个最小生成树没有用过的边,排个序,最后就比较两个最小生成树加1,2…条边后谁更小,就输出谁。
Code:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int AX = 1e2+66;
struct Node{
    int u , v , w ;
    int id ;
    bool friend operator < ( const Node &a , const Node &b ){
        return a.w < b.w ;
    }
}a[AX] , b[AX] , c[AX];
int pa[AX];
int pb[AX];
int vis[5][AX];
Node roada[AX];
Node roadb[AX];
int n , m ;
int tot1 , tot2;
void init(){
    for( int i = 1 ; i <= n ; i++ ){
        pa[i] = i ; pb[i] = i ;
    }
    memset( vis , 0 , sizeof(vis) );
    tot1 = 0 ;
    tot2 = 0 ;
}

int find_a( int x ){
    return x == pa[x] ? pa[x] : pa[x] = find_a(pa[x]);
}

int find_b( int x ){
    return x == pb[x] ? pb[x] : pb[x] = find_b(pb[x]);
}

void mix_a( int x , int y ){
    int xx = find_a(x); int yy = find_a(y);
    if( xx != yy ) pa[yy] = xx ;
}

void mix_b( int x , int y ){
    int xx = find_b(x); int yy = find_b(y);
    if( xx != yy ) pb[yy] = xx ;
}

int Kruscal( Node *e , int k , int id ){
    int ans = 0 ;
    int nEdge = 0 ;
    sort( e , e + k );
    for( int i = 0 ; i < k ; i++ ){
        if( !id ){
            if( find_a(e[i].u) != find_a(e[i].v) ){
                mix_a( e[i].u , e[i].v );
                ans += e[i].w;
                vis[id][e[i].id] = 1 ;
                nEdge ++;
            }
        }else{
            if( find_b(e[i].u) != find_b(e[i].v) ){
                mix_b( e[i].u , e[i].v ); 
                ans += e[i].w;
                vis[id][e[i].id] = 1 ;
                nEdge ++;
            }
        }
    }
    if( nEdge < n - 1 ) ans = INF ;
    return ans ;
}

int main(){
    int T;
    scanf("%d",&T);
    int Case = 0 ;
    while( T-- ){
        scanf("%d%d",&n,&m);
        init();
        char col[5];
        for( int i = 1 ; i <= m ; i++ ){
            scanf("%d%d%d%s",&c[i].u,&c[i].v,&c[i].w,col);
            if( col[0] == 'R' || col[0] == 'G' ){
                a[tot1].u = c[i].u ; a[tot1].v = c[i].v; a[tot1].w = c[i].w ; a[tot1++].id = i;
            }        
            if( col[0] == 'B' || col[0] == 'G' ){
                b[tot2].u = c[i].u ; b[tot2].v = c[i].v; b[tot2].w = c[i].w ; b[tot2++].id = i;
            }
        }
        int ans = Kruscal(a,tot1,0);
        int ans1 = Kruscal(b,tot2,1);

        int num = 0 ;
        int cnt = 0 ;

        for( int i = 1 ; i <= m ; i++ ){
            if( !vis[0][i] ){
                roada[num++] = c[i];
            }
        }
        sort( roada , roada + num );

        for( int i = 1 ; i <= m ; i++ ){
            if( !vis[1][i] ){
                roadb[cnt++] = c[i];
            }
        }
        sort( roadb , roadb + cnt ) ;

        printf("Case #%d:\n",++Case);
        if( ans1 == INF && ans == INF ){
            for( int i = 0 ; i < m ; i++ ){
                printf("-1\n");
            }continue;
        }
        for( int i = 1 ; i < n - 1 ; i++ ){
            printf("-1\n");
        } 
        printf("%d\n",min(ans1,ans));
        int t = 0 ;
        int t1 = 0 ;
        for( int i = n ; i <= m ; i++ ){
            ans += roada[t++].w;
            ans1 += roadb[t1++].w;
            printf("%d\n",(ans<ans1?ans:ans1));
        }
    }
    return 0 ;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值