2.20 CW 模拟赛 赛时记录
前言
状态真的不算好, 但是无所谓了, 按照策略做就行了
看题
给我干哪来了, 这是 \(\textrm{Day 2}\) ?
\(\rm{T1}\)
最好和期望没啥关系
\(\rm{T2}\)
不太好想
\(\rm{T3}\)
多半是数学题了
总而言之, 不贪跟策略, 数据检验很重要
身体很重要, 秋裤还是穿上吧
\(\rm{T1}\)
思路
题意
个单元, 第 个单元的权重为
每秒在余下的单元中, 以 的概率选择第 个单元删除即
求第 个单元被删除时间的期望
时间上限为 \(n\) , 可以接受
不难想到 \(\rm{dp}\)
令 \(f_{i, j}\) 表示第 \(i\) 秒时 \(\sum w = j\) 的概率
什么你说 \(j\) 开不了, 超冰啊, 用 \(\rm{map}\) 即可
考虑转移
你发现这个东西假假假, 怎么办, 虽然说改成状态压缩可以过掉 \(\textrm{20 pts}\)
重新设计, 考虑 \(\mathcal{O} (n)\) 计算在 \(t \in [1, n]\) 中删除 \(1\) 单位的概率
好吧真的不太会, 没有搞出什么性质, 把部分分打了跑路
\(n \leq 20\)
令 \(f_{i, \mathbb{S}}\) 表示第 \(i\) 秒时状态为 \(\mathbb{S}\) 的概率
考虑转移
如何统计答案?
枚举 \(1\) 号节点被删除的时间 \(t\)
总时间复杂度 \(\mathcal{O} (n 2^n)\) , 经典
\(w_{\mathbb{S}}\) 考虑预处理, 不然复杂度劣了
\(a_i = 1\)
这种情况怎么做?
不难发现每次删除都是等概率的
所以需要计算 \(1\) 单元存活到时间 \(i\) 的概率, 等效于在 \(n - 1\) 个单元中选择 \(i - 1\) 个杀掉的概率
不难发现就是
感觉不太对, 但是先把 \(\textrm{20 pts}\) 打了可以验证这档分
实现
框架
按照上面实现即可, 但是出题人为什么不给模数, 无敌了
代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e5 + 20; // 改改改
const int MAXS = (1 << 20) + 1; // 改改改
int n;
int w[MAXN];
/*状态压缩 dp*/
class subtask1 {
private:
struct node { int first, second; };
double f[2][MAXS]; int now = 0, nxt = 1;
std::basic_string<int> size[MAXN];
std::basic_string<node> st[MAXS];
int sigma[MAXS];
void init() {
// for (int S = 0; S < (1 << n); S++) st[S].clear(), sigma[S] = 0;
// for (int i = 0; i <= n; i++) size[i].clear();
for (int S = 0; S < (1 << n); S++) {
size[__builtin_popcount(S)].push_back(S);
for (int i = 1; i <= n; i++) {
if ((S >> (i - 1)) & 1) { sigma[S] += w[i]; continue; }
st[S].push_back({(S | (1 << (i - 1))), i});
}
}
}
public:
void solve() {
init();
double ans = 0;
memset(f, 0, sizeof f);
f[now][(1 << n) - 1] = 1.0;
ans += f[now][(1 << n) - 1] / sigma[(1 << n) - 1] * w[1];
for (int i = 1; i <= n; i++) {
std::swap(now, nxt);
for (int S : size[n - i]) {
for (node Spre : st[S])
f[now][S] += f[nxt][Spre.first] / sigma[Spre.first] * w[Spre.second];
if (S & 1) ans += f[now][S] * (i + 1) / sigma[S] * w[1];
}
}
printf("%.10f", ans);
}
} sub1;
/*ai = 1*/
class subtask3 {
private:
public:
void solve() {
double ans = 0;
for (int i = 1; i <= n; i++) ans += i * 1.0 / n;
printf("%.10f", ans);
}
} sub3;
signed main()
{
scanf("%lld", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &w[i]);
if (n <= 20) sub1.solve();
else sub3.solve();
return 0;
}
\(\rm{T2}\)
时间分配有一点问题, 先看这个, 避免上面代码太难打影响心态
还好只有 \(3\) 题
思路
先考虑部分分, 因为时间的问题
考虑 \(a_i\) 确定时怎么做, 不难发现枚举 \(x\) 可以让 \(a_i\) 固定
不难发现转移
令 \(f_{i, j}\) 表示前 \(i\) 条信息分了 \(j\) 个段的最小花费
然后就是典中典
\((a_i + x) \textrm{ mod } m\) 是不是有什么 \(\rm{trick}\) 啊, 但是我不到啊
可以乱搞出 \(\mathcal{O} (nm \log nm)\) , 差不多过掉 \(60\%\)
实现
框架
首先枚举 \(x\) , 计算 \(a_i\) , \(\textrm{sum}\) , 二分计算最优解, 不同之间取最小即可
代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e3 + 20; // 改改改
int n, m, k;
int base[MAXN], w[MAXN];
int ans = 0x3f3f3f3f;
bool check(int x) {
int sum = 0, tmp = 0;
for (int i = 1; i <= n; i++) {
if (w[i] > x) return false;
sum += w[i]; if (sum > x) tmp++, sum = w[i];
}
return (tmp + 1) <= k;
}
int solve(int x) {
/*处理当前 a*/ for (int i = 1; i <= n; i++) w[i] = ((base[i] + x) >= m ? base[i] + x - m : base[i] + x);
int left = 0, right = n * m + 1, mid, res = 0x3f3f3f3f;
while (left < right) {
mid = left + (right - left) / 2;
if (check(mid)) res = mid, right = mid;
else left = mid + 1;
}
return res;
}
signed main()
{
scanf("%lld %lld %lld", &n, &m, &k);
for (int i = 1; i <= n; i++) scanf("%lld", &base[i]);
for (int x = 0; x < m; x++) ans = std::min(ans, solve(x));
printf("%lld", ans);
return 0;
}
\(\rm{T3}\)
那我问你, 我这样子打得完代码就有鬼了\((\)谢谢 上帝/一个高尚无比美丽动人温文尔雅的名字 帮我把鬼打掉了, 请您帮我把挂分也打掉吧\()\)
虽然说这样不太好, 但是这场先这样, 节奏保持
思路
\(30\) 纯送
再来一档到达大众分, 但是我感觉以我的数学水平不太可能
好的没思路, 确实不太会, 先开始打, 最后过来干这个
好好好, 上帝啊, 让我再拼一档分吧
算了 上帝/一个高尚无比美丽动人温文尔雅的名字 你别帮了, 我不要在这个事上掉功德, 但是很感谢你帮我打完代码, 谢谢谢谢谢
一个合法的子集需要满足
- 集内数互质
- 集内出现的质数集合之积等于 \(n\)
对 \(n\) 分解质因数, 确定质数集合
不对, 会不会有其他质数集合之积也等于 \(n\) , 哦, 唯一分解定理证明了不会
所以我们对于集合中的每一个数, 可以拼给他一些质因数, 最多只有 \(20 \sim 30\) 个不同的质因数
那么显然可以进行一个状态压缩 \(\rm{dp}\) , 每次从质数集合之中拿出一个子集, 拼成一个数
遗憾, 没搞出来

浙公网安备 33010602011771号