2022.7.28 模拟赛
T1 数据结构
第一个操作很好实现,只需要增加 \(x^k\) 即可
第二个操作是瓶颈,暴力做是不行的
瓶颈在操作 \(2\),若是可以减少操作 \(2\) 的复杂度,就可以通过本题
怎么做呢?我们知道每个数到底被加了几次,就可以一次性算出它的贡献
我们每次操作 \(2\) 使用一个懒标记,加入 \(x\) 就是加入 \(x-tag\)
预处理组合数,如此一来复杂度就在 \(O(mk)\) 了
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
inline int rd()
{
int ret = 0, f = 1;
char c;
while (c = getchar(), !isdigit(c))
f = c == '-' ? -1 : 1;
while (isdigit(c))
ret = ret * 10 + c - '0', c = getchar();
return ret * f;
}
const int MAXN = 55;
const int MOD = 1e9 + 7;
int k, m, sum[MAXN], f[MAXN][MAXN];
int s;
void solve()
{
int x, y;
y = rd();
if (y == 0)
{
x = rd() - s;
if (x < 0)
x += MOD;
int tmp = 1;
for (int i = 0; i <= k; i++)
{
(sum[i] += tmp;) %= MOD;
(tmp *= x) %= MOD;
}
}
else
s++;
int ans = 0, tmp = 1;
for (int i = k; i >= 0; i--)
{
(ans += (f[k][i] * sum[i]) % MOD * tmp % MOD) %= MOD;
(tmp *= s) %= MOD;
}
cout << ans << endl;
}
signed main()
{
m = rd();
k = rd();
f[0][0] = 1;
for (int i = 1; i <= 50; i++)
{
f[i][0] = 1;
for (int j = 1; j <= 50; j++)
{
f[i][j] = (f[i - 1][j] + f[i - 1][j - 1]) % MOD;
}
}
while (m--)
solve();
return 0;
}
T2 商店购物
考虑一个判定问题:给出一个 \([k,2k]\) 判断是否可行。
对于每个元素 \(x\) :
若 \(x>2k\) ,则 \(x\) 对判定 \(k\) 无效
若 \(x∈[k,2k]\) ,则 \(k\) 一定可行
若 \(x<k\),需要继续讨论
我们关注小于 \(k\) 的 \(x\) ,记它们的和 \(\sum_{x<k}{x}\) 为 \(S\)
若 \(S<2k\),则 \(k\) 不可行
若 \(S∈[k,2k]\) ,则 \(k\) 可行(我们找到了一种方案)
若 \(S>2k\) ,则 \(k\) 可行
为什么?因为每个 \(x\) 均小于 \(k\) ,记 \(s'=s-x\)
\(S'>k\) 恒成立,故 \(S'\) 不可能小于 \(k\) 而使得 \(k\) 不可行
若 \(S'∈[k,2k]\),则 \(k\) 可行(我们找到了一种方案)
若 \(S'>2k\) ,那么可以继续重复此过程,直到可行
因此,只需要记录前缀和,算出所有的区间并,就是最终答案
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int MAXN = 100005;
int a[MAXN], n, ans;
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + 1 + n);
int sum = 0, lst = 0;
for (int i = 0; i <= n; i++)
{
sum += a[i];
while (a[i + 1] == a[i])
sum += a[++i];
ans += sum - max(lst, (a[i] + 1) / 2 - 1);
lst = sum;
}
cout << ans << endl;
return 0;
}
T3 植物大战僵尸
什么时候玩家必败?
如果存在形如 \(11,2222,33333333,...\) 的情况,那么玩家显然必败
也就是说,如果有 \(2^n\) 个 \(n\) 位置的僵尸,那么玩家是必败的
我们发现,位置 \(n\) 的权重是 \(n-1\) 位置的一半,换句话说,后面最多只有一半的僵尸能到达下一个位置
我们可以把这个序列用每个元素的权重表示出来,如果我们认为 \(n\) 的权重是 \(2^n\) 的话,那么玩家必败当且仅当我们可以把序列分成两部分 \(A,B\),使得 \(A≥\frac{1}{2}\) 且 \(B≥\frac{1}{2}\)
于是现在的问题就是,给定 \(n\) 个分数组成的集合,求把它们划分为两个 \(sum≥\frac{1}{2}\) 的集合的方案数
这样不好求,可以容斥一下,求把它们划分成至少有一个 \(sum<\frac{1}{2}\) 的方案数,这就是一个背包问题了
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
inline int rd()
{
int ret = 0, f = 1;
char c;
while (c = getchar(), !isdigit(c))
f = c == '-' ? -1 : 1;
while (isdigit(c))
ret = ret * 10 + c - '0', c = getchar();
return ret * f;
}
void _(int x)
{
if (x == 0)
return;
_(x / 10);
putchar('0' + x % 10);
}
void out(int x)
{
if (!x)
return;
_(x);
}
const int MAXN = 100005;
const int MOD = 1e9 + 7;
int T, n;
int a[MAXN], f[MAXN];
void init()
{
memset(f, 0, sizeof(f));
}
int qpow(int x, int y)
{
int ret = 1ll;
while (y)
{
if (y & 1)
(ret *= x) %= MOD;
(x *= x) %= MOD;
y >>= 1ll;
}
return ret;
}
void solve()
{
n = rd();
for (int i = 1; i <= n; i++)
a[i] = rd();
sort(a + 1, a + 1 + n);
int V = 0, cur = a[n];
f[0] = 1;
for (int i = n; i >= 1; i--)
{
int t = 1;
while (a[i] != cur)
{
t <<= 1;
cur--;
if (t > V)
{
cur = a[i];
break;
}
}
if (t > 1)
{
for (int k = 0; k <= V / t; k++)
{
int tmp = 0;
for (int j = k * t; j < min((k + 1) * t, V + 1); j++)
{
tmp += f[j];
f[j] = 0;
tmp %= MOD;
}
f[k] += tmp;
f[k] %= MOD;
}
V /= t;
}
for (int j = V; j >= 0; j--)
f[j + 1] += f[j], f[j + 1] %= MOD;
V++;
}
int up = 1, ans = 0;
while (cur > 1)
{
up <<= 1;
up = min(up, V + 1);
cur--;
}
for (int i = 0; i < up; i++)
(ans += f[i]) %= MOD;
ans = -2 * ans;
ans += qpow(2, n);
while (ans < 0)
ans += MOD;
out(ans);
putchar('\n');
}
signed main()
{
T = rd();
while (T--)
init(), solve();
return 0;
}
T4 机房的新生活委员
设 \(f(i,j)\) 表示前 \(i\) 个商店买状态为 \(j\) 花的钱的最小值
则有转移方程 \(f(i,j)=min{f(i-1,j),f(i-1,j)+fee_i+min{f(i,p)+cost_{i,k}}}\)
初始状态 \(f(0,0)=0\) ,其他为 \(inf\)
最终答案为 \(f(n,2^m-1)\) , 时空复杂度为 \(O(nm2^m)\)
#include <cstdio>
#include <cstring>
using namespace std;
#define cmin(_a, _b) (_a > (_b) ? _a = (_b) : 0)
int fee[108], cost[108][18], tmp[108], f[66600], v[66600];
int main()
{
register int n, m, i, j, S, x, _S, U, newf, newfS;
scanf("%d %d", &n, &m);
for (i = 1; i <= n; ++i)
{
scanf("%d", &fee[i]);
for (j = 0; j < m; ++j)
scanf("%d", &cost[i][j]);
}
U = (1 << m) - 1;
for (S = 1; S <= U; ++S)
{
memcpy(tmp, fee, sizeof(tmp) / 108 * (n + 1));
_S = S;
for (i = 0; _S; ++i, _S >>= 1)
{
if ((_S & 1) == 0)
continue;
for (j = 1; j <= n; ++j)
tmp[j] += cost[j][i];
}
v[S] = tmp[1];
for (i = 2; i <= n; ++i)
cmin(v[S], tmp[i]);
}
memset(f, 63, sizeof(f));
f[0] = 0;
for (S = 1; S <= U; ++S)
{
newfS = 0x3f3f3f3f;
for (x = S; x; x = S & (x - 1))
{
newf = f[S ^ x] + v[x];
cmin(newfS, newf);
}
f[S] = newfS;
}
printf("%d", f[U]);
return 0;
}