[赛记] 暑假集训CSP提高模拟24
与和 100pts
签到题但还是做了很久。。。
考虑与的条件,可以发现,如果将 $ a $ 转化成二进制,那么二进制上为 $ 1 $ 的位置 $ x $ 和 $ y $ 都必须是 $ 1 $,所以首先将 $ s $ 减去 $ 2 \times a $,然后再判断一下 $ (s - 2 \times a) \operatorname{and} a $ 是否为 $ 0 $ 即可;
赛时用唯一分解定理做的,打了61行,确实麻烦了,但更好理解;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int t;
long long a, s;
long long cnt, ma, acnt, bcnt;
long long o;
bool vis[105];
int main() {
freopen("and.in", "r", stdin);
freopen("and.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> t;
while(t--) {
cin >> a >> s;
cnt = 0;
memset(vis, 0, sizeof(vis));
ma = 0;
o = 0;
acnt = 0;
bcnt = 0;
long long aa = a;
long long bb = s;
while(aa) {
acnt++;
aa >>= 1;
}
while(bb) {
bcnt++;
bb >>= 1;
}
acnt--;
bcnt--;
ma = max(acnt, bcnt);
while(a) {
if (a & 1) {
cnt += (1ll << o);
vis[o] = true;
}
a >>= 1;
o++;
}
if (2 * cnt > s) {
cout << "No" << '\n';
continue;
}
s -= 2 * cnt;
for (int i = ma; i >= 0; i--) {
if (vis[i]) continue;
if (s >= (1ll << i)) {
s -= (1ll << i);
}
}
if (s == 0) cout << "Yes" << '\n';
else cout << "No" << '\n';
}
return 0;
}
函数 0pts
赛时没看;
考虑怎样才能使套出来的的函数值尽可能大;
设 $ f_1(x) = a_1x + b_1 \ \ \ \ \ f_2(x) = a_2x + b_2 $,则若有 $ f_1(f_2(x)) > f_2(f_1(x)) $,那么需要满足的条件为:
这样,我们想让 $ f_2 $ 在前面,所以我们要按 $ b_1(a_2 - 1) > b_2(a_1 - 1) $ 排序,然后DP即可;
具体的,设 $ f_i $ 表示确定了 $ i $ 个函数的最大值,按照01背包转移即可;
时间复杂度:$ \Theta(nk) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
int n, k;
struct sss{
int a, b;
}e[5000005];
int f[5000005];
bool cmp(sss x, sss y) {
return x.b * (y.a - 1) > y.b * (x.a - 1);
}
main() {
freopen("func.in", "r", stdin);
freopen("func.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> e[i].a >> e[i].b;
}
sort(e + 1, e + 1 + n, cmp);
f[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = k; j >= 1; j--) {
f[j] = max(f[j], f[j - 1] * e[i].a + e[i].b);
}
}
cout << f[k];
return 0;
}
袋鼠 0pts
赛时做了3h,结果一分没得。。。
预设性DP(早就忘了。。。)
设 $ f_{i, j} $ 表示填了前 $ i $ 个数,有 $ j $ 个连续段的方案数,特别的,序列开头是 $ s $ ,结尾是 $ t $;
考虑转移:
因为要满足左右跳的限制,所以不能填在连续段的左右两端;
- 新开一段;
上一次有 $ j - 1 $ 个段,所以一共有 $ (j - 2) + 2 = j $ 个位置可以新开段(加二是因为头和尾能放),特别的,$ i > s $ 说明头不能放, $ i > t $ 说明尾不能放;
- 连接两个连续段;
上一次有 $ j + 1 $ 个连续段,所以有 $ j $ 个空位可以放;
- $ i = s $ 或 $ i = t $;
这时只有一个地方可以放,所以把上面的系数全变成 $ 1 $ 然后相加即可;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const long long mod = 1e9 + 7;
int n, s, t;
long long f[2005][2005];
int main() {
freopen("kang.in", "r", stdin);
freopen("kang.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> s >> t;
f[1][1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == s || i == t) {
f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) % mod;
continue;
}
f[i][j] = (f[i][j] + 1ll * (j - (i > s) - (i > t)) * f[i - 1][j - 1] % mod) % mod;
f[i][j] = (f[i][j] + 1ll * j * f[i - 1][j + 1] % mod) % mod;
}
}
cout << f[n][1];
return 0;
}