Codeforces Round #247 Div2

题目链接:CF247|DIV2

CF小号已经连跌两场了, 这一场做着做着竟然睡着了。。。

顺便:对于gvim的新手来说, 刚开始用VIM的确是觉得交互有些麻烦, sad...

A:简单题。略过

#include<cstdio>
#include<iostream>
#include<cstring>
#include<iostream>
using namespace std;
string s;
int a[5];

int main(){
    for(int i = 1; i <= 4; ++i){
        scanf("%d", &a[i]);
    }

    int n, ans = 0;
    cin >> s;
    n = s.size();

    for(int i = 0; i < n; ++i){
        ans += a[s[i] - '0'];
    }

    cout << ans << endl;
    return 0;
}

B:简单题。可以dfs一下5!种全排列,然后模拟一下各种排列的值比较一下。 如果数据很大怎么搞? 贪心or?

#include<cstdio>
#include<iostream>
#include<cstring>
#include<iostream>
using namespace std;
int a[6][6];
int b[5];
bool vis[5];
int ans;

int foo(){
    int ret = 0;
    ret += (a[b[0]][b[1]] + a[b[1]][b[0]]);
    ret += (a[b[2]][b[3]] + a[b[3]][b[2]]);
    ret += (a[b[2]][b[1]] + a[b[1]][b[2]]);
    ret += (a[b[3]][b[4]] + a[b[4]][b[3]]);
    ret += (a[b[2]][b[3]] + a[b[3]][b[2]]);
    ret += (a[b[4]][b[3]] + a[b[3]][b[4]]);
    return ret;
}

void dfs(int src){
    if(src == 5){
        int ret = foo();
        if(ret > ans) ans = ret;
        return ;
    }
    
    for(int i = 1; i <= 5; ++i){
        if(vis[i] == 0){
            vis[i] = 1;
            b[src] = i;
            dfs(src + 1);
            vis[i] = 0;
        }
    }
}

int main(){
    for(int i = 1; i <= 5; ++i){
        for(int j = 1; j <= 5; ++j)
                scanf("%d", &a[i][j]);
    }
    ans = -1e9;
    dfs(0);
    printf("%d\n", ans);
    return 0;
}

C:简单背包。 深挖起来这题与传统的背包的区别在于:对于每一层上面的物品必须要选, 而以前的背包就不需要满足。 所以DP要开二维DP, 一维DP是无法清楚的表示状态的! 具体做法是:分两次DP, 分别DP出每一个节点值都不超过K和每一个节点值都不超过d-1的情况, 然后作差就可以求出至少有一个节点值在[d, k]之间的情况了。

这种作差的思想在算法中经常被用到。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 110;
int dp[N][N];
const int MOD = 1e9 + 7;

int main(){
    int n, t, d;
    scanf("%d %d %d", &n, &t, &d);
    dp[0][0] = 1;
    if(d > t) {
        printf("0\n");
        return 0;
    }
    
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            for(int k = j - 1; k >= 0 && (j - k) <= t; --k){
                dp[j][i] += dp[k][i-1];
                dp[j][i] %= MOD;
            //  printf("y\n");
            }
        }
    }

    int tmp = 0;
    for(int i = 1; i <= n; ++i){
        tmp += dp[n][i];
        tmp %= MOD;
    }

    memset(dp, 0, sizeof(dp));
    dp[0][0] = 1;
    
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            for(int k = j - 1; k >= 0 && (j - k) < d; --k){
                dp[j][i] += dp[k][i-1];
                dp[j][i] %= MOD;
            }
        }
    }

    int tmp_ = 0;
    for(int i = 1; i <= n; ++i){
        tmp_ += dp[n][i];
        tmp_ %= MOD;
    }

    printf("%d\n", (tmp - tmp_ + MOD) % MOD);
    return 0;
}


D:题意:求一个n,使得n+1到2n这些数的二进制中恰好有k个1的数有m个。
分析:

1. 这题竟然具有二分的性质(可以打表观察), 对于同一个k, 当n1 < n2时, n1~2n1中满足条件的数字个数不会比n2~2n2多。
2. 有了二分的特性之后,问题转化为:给出x,如何求[x, x*2]之间满足条件的个数, 假设:cal(x)表示[0,x]满足条件的个数,那么就是求cal(x*2) - cal(x)。
3. 如何求cal(x)? 举个例子:假设X的二进制形式为:101010, 因为是求[0,x]中满足条件的情况,也就是 不能比x大, 那么我们可以这样考虑, 从高位往地位遍历, 假设走到i位,我们如何构造比x小的数? 可以保持i前面的数字不动, 如果此时该位为1的话, 我们可以将他变成0, 如果是0,那么我不能改变, 否则构成的数肯定比x大,这样我们就可以构成一个不超过x的数字。 然后假设前x位出现了num个1, 因为要有k个1,所以剩下的位数里面要有k-num个1,这就是组合数学了。 详细见代码。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 71;
LL c[N][N];
LL n, m, k;

LL gao(LL x){
    LL ans = 0;
    int num = 0;
    for(LL i = 60; i >= 0; --i){
        if(x & (1LL << i)){
            if(k - num >= 0){
                ans += c[i][k-num];
            }
            ++num;
        }
    }   
    return ans;
}

int main(){
    for(int i = 0; i <= 62; ++i) c[i][0] = 1;
    for(int i = 1; i <= 62; ++i){
        for(int j = 1; j <= i; ++j){
            c[i][j] = c[i-1][j-1] + c[i-1][j];
        }
    }

    scanf("%I64d%I64d", &m, &k);
    LL l = 1, r = (LL)1e18;
    while(l <= r){
        LL mid = (l + r) >> 1;
        LL tmp = gao(mid * 2) - gao(mid);
        if(tmp == m){
            n = mid;
            break;
        }
        else if(tmp > m){
            r = mid - 1;
        }
        else l = mid + 1;
    }
    printf("%I64d\n", n);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值