状压 dp + 枚举子集,二项式定理,贪心,思维 + 背包
# A. 机房的新生活委员(cen)
由于把这道题看成 T4,只来得及写 的暴力 /kk
非常板子的状压 ,先考虑暴力,设 表示前 家商店,买了状态为 的物品最小花费。每家商店都是可买可不买。
转移方程:
枚举在第 个商店买了什么,转移就行了。
但是直接枚举两个集合的话复杂度是 的,我们先枚举 再枚举 的子集,就变成 了。
现在外面还有个 的枚举,里面 暴力计算的话还要再多一个 。
事实上我们可以预处理 表示状态 的物品在 个商店中的最小花费。注意要加上 ,因为是在 ,所以多加了也无所谓。
预处理的复杂度是 ,可以接受。
总复杂度就是 .
#include <bits/stdc++.h> | |
#define pb push_back | |
#define pc putchar | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 110; | |
const int M = (1 << 16) + 10; | |
const int inf = 1e9; | |
int n, m; | |
int w[N], c[N][20]; | |
int f[M], g[N][M], val[M], id[M]; | |
signed main(){ | |
// freopen("test.in", "r", stdin); | |
// freopen("test.out", "w", stdout); | |
n = read(), m = read(); | |
for(int i = 1; i <= n; ++i){ | |
w[i] = read(); | |
for(int j = 1; j <= m; ++j) c[i][j] = read(); | |
} | |
int sta = (1 << m) - 1; | |
for(int i = 1; i <= n; ++i) | |
for(int s = 0; s <= sta; ++s){ | |
g[i][s] = w[i]; | |
for(int j = 1; j <= m; ++j) | |
if(s & (1 << (j - 1))) g[i][s] += c[i][j]; | |
} | |
for(int s = 0; s <= sta; ++s) g[0][s] = inf; | |
for(int s = 1; s <= sta; ++s){ | |
int p = 0; | |
for(int i = 1; i <= n; ++i) | |
if(g[i][s] < g[p][s]) p = i; | |
val[s] = g[p][s], id[s] = p; | |
} | |
memset(f, 0x3f, sizeof(f)); | |
for(int i = 0; i <= sta; ++i) f[i] = val[i]; | |
for(int s = 1; s <= sta; ++s) | |
for(int t = s; t; t = (t - 1) & s) | |
f[s] = min(f[s], f[s ^ t] + val[t]); | |
write(f[sta]), puts(""); | |
return 0; | |
} |
# B. 数据结构(set)
考虑维护 ,简单来说就是把 次方和全都维护出来。
再来看两种操作:
插入 :直接枚举 , 即可。
整体加 :考虑二项式定理。
维护所有的 即可。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define pc putchar | |
#define int long long | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 2e5 + 10; | |
const int mod = 1e9 + 7; | |
int n, k, a; | |
inline int add(int x) {return x >= mod ? x - mod : x;} | |
inline int sub(int x) {return x < 0 ? x + mod : x;} | |
inline int mul(int x, int y) {return 1ll * x * y % mod;} | |
inline int qpow(int a, int b){ | |
int res = 1; | |
while(b){ | |
if(b & 1) res = mul(res, a); | |
a = mul(a, a), b >>= 1; | |
} | |
return res; | |
} | |
inline int inv(int x) {return qpow(x, mod - 2);} | |
int c[60][60], sum[60]; | |
inline void init(int n){ | |
for(int i = 0; i <= n; ++i) c[i][0] = 1; | |
for(int i = 1; i <= n; ++i) | |
for(int j = 1; j <= i; ++j) | |
c[i][j] = add(c[i - 1][j - 1] + c[i - 1][j]); | |
} | |
inline void solve_0(int x){ | |
for(int i = 0; i <= k; ++i) sum[i] = add(sum[i] + qpow(x, i)); | |
} | |
int tmp[60]; | |
inline void solve_1(){ | |
for(int i = 0; i <= k; ++i){ | |
tmp[i] = 0; | |
for(int j = 0; j <= i; ++j) | |
tmp[i] = add(tmp[i] + mul(c[i][j], sum[j])); | |
} | |
for(int i = 0; i <= k; ++i) sum[i] = tmp[i]; | |
} | |
signed main(){ | |
// double st = clock(); | |
n = read(), k = read(), init(50); | |
for(int i = 1; i <= n; ++i){ | |
int op = read(), x; | |
if(!op) solve_0(read()); | |
else solve_1(); | |
write(sum[k]), puts(""); | |
} | |
// double ed = clock(); | |
// cerr << ed - st << endl; | |
return 0; | |
} |
# C. 商店购物(sum)
首先对 从小到大排序,对于每个 依次考虑对应的 的区间。
如果不考虑可以加到一起的话,答案就是这些个区间求交。
可以加怎么办呢?把 加起来看看?
由于 ,不难发现,。
这样一来, 对应的 的区间就和 对应的区间合并到一起了。
所以我们可以直接把 对应的区间改写成这样:
推出:
不难证明,这样的区间并是不会漏的,所以排序做个前缀和再求区间并就完了。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define pc putchar | |
#define int long long | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 1e5 + 10; | |
int n, ans; | |
int a[N], sum[N]; | |
signed main(){ | |
// freopen("sum.in", "r", stdin); | |
// freopen("sum.out", "w", stdout); | |
n = read(); | |
for(int i = 1; i <= n; ++i) a[i] = read(); | |
sort(a + 1, a + 1 + n); | |
for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + a[i]; | |
ans = a[1] - (a[1] + 1) / 2 + 1; | |
int mx = a[1]; | |
for(int i = 2; i <= n; ++i){ | |
if(mx >= (a[i] + 1) / 2) ans += sum[i] - mx; | |
else ans += sum[i] - (a[i] + 1) / 2 + 1; | |
mx = sum[i]; | |
} | |
write(ans), puts(""); | |
return 0; | |
} |
# D. 植物大战僵尸(pvz)
考场上推了很久也没什么思路,一直以为要推 函数什么的……
事实上就一个背包就完了。
先考虑必胜条件,对于位置 ,如果有 个僵尸则可以保证必胜。
此时位置 上有 只僵尸,占比就是 ,然后我们要把这些数的和分成两个数,使得两个数都大于等于 ,求方案数。
利用容斥的思想,求至少有 1 个小于 的方案数。
为了方便实现,我们把权重全都乘上 ,然后让两个数都大于等于 。
设 表示和为 ,且第 个位置上有 个僵尸时的方案数。
转移过程中枚举从下一个位置走过来的僵尸数量背包即可。
最后的答案就是 。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define ll long long | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0, f = 1; | |
char ch = getchar(); | |
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x * f; | |
} | |
template <typename T> inline void write(T x){ | |
if(x < 0) putchar('-'), x = -x; | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 2010; | |
const int mod = 1e9 + 7; | |
int T, n; | |
ll sum; | |
int a[N], buk[40], f[40][N], c[N][N]; | |
//f [i][j]: 僵尸个数 <= 2^i 且第 i 个位置上有 j 个僵尸的方案数 | |
inline int add(int x) {return x >= mod ? x - mod : x;} | |
inline int sub(int x) {return x < 0 ? x + mod : x;} | |
inline int mul(int x, int y) {return 1ll * x * y % mod;} | |
inline int qpow(int a, int b){ | |
int res = 1; | |
while(b){ | |
if(b & 1) res = mul(res, a); | |
a = mul(a, a), b >>= 1; | |
} | |
return res; | |
} | |
inline void init(int n){ | |
for(int i = 0; i <= n; ++i) c[i][0] = 1; | |
for(int i = 1; i <= n; ++i) | |
for(int j = 1; j <= i; ++j) c[i][j] = add(c[i - 1][j - 1] + c[i - 1][j]); | |
} | |
inline void solve(){ | |
memset(buk, 0, sizeof(buk)); | |
memset(f, 0, sizeof(f)); | |
n = read(), sum = 0; | |
for(int i = 1; i <= n; ++i) a[i] = read(), buk[31 - a[i]]++; | |
for(int i = 1; i <= 30; ++i) sum += (1ll << i) * buk[i]; | |
if(sum < (1ll << 31)) return puts("0"), void(); | |
f[0][0] = 1; // 容斥,计算分成两半至少有一边僵尸 <= 2^30 的方案数 | |
for(int i = 1; i <= 30; ++i) | |
for(int j = 0; j <= buk[i]; ++j) | |
for(int k = 0; k <= 2000; ++k){ | |
int x = j + k / 2; | |
f[i][x] = add(f[i][x] + mul(f[i - 1][k], c[buk[i]][j])); | |
} | |
write(sub(qpow(2, n) - mul(2, f[30][0]))), puts(""); | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
T = read(), init(2000); | |
while(T--) solve(); | |
return 0; | |
} |