题目链接: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;
}