CF525E 题解
前言
比较套路的折半搜索。代码实现略微繁琐。
思路
每个数有三个状态:不选、选
数据范围
折半搜的含义很显然:把序列分成两半,左右分别爆搜,再考虑合并左右答案。
首先看左边的搜索。这个很容易,不就是普通的爆搜吗!
int n, k, a[N], mid; //mid = n / 2
LL s, ans;
short choose[N];
LL fac(int x) //求 x! 如果太大了(大于s了)就返回 -114514
{
LL mul = 1;
for (int i = x; i; i--) //太大时,倒序枚举可以更快返回-114514
{
if (mul > s / i) return -114514;
//这里有一个细节,需要先判-114514再乘。
//因为mul很容易爆LL,一不小心乘个i,可能就爆了。
//因此,if (mul * i > s) 可以移项转化为 if (mul > s / i) 就万无一失了。
//当然,直接开__int128也行,毕竟现在CCF允许使用它。
mul *= i;
}
return mul;
}
map <pil, int> mp; //mp[mk(cnt, sum)]表示:使用了多少次阶乘、和是多少
map <LL, bool> vis; //vis[sum]表示:sum有无出现过
void chk()
{
LL sum = 0;
int cnt = 0;
for (int i = 1; i <= mid; i++)
if (choose[i] == 1)
{
if (sum + a[i] > s) return;
sum += a[i];
}
else if (choose[i] == 2)
{
LL ai = fac(a[i]);
if (ai == -114514 || cnt + 1 > k || sum + ai > s) return; //看有没有超过k和s
sum += ai, cnt++;
}
mp[mk(cnt, sum)]++, vis[sum] = true;
}
void dfs(int x)
{
if (x > mid) {chk(); return;}
for (int i = 0; i < 3; i++) choose[x] = i, dfs(x+1); //0表示不选,1表示选a[x],2表示选a[x]阶乘
}
然后考虑右边的爆搜。
void DFS(int x) //和dfs()极其相似,只不过更改了边界条件与chk()
{
if (x > n) {CHK(); return;}
for (int i = 0; i < 3; i++) choose[x] = i, DFS(x+1);
}
和
理解了左边就很容易理解右边。
void CHK() //和chk()极其相似,只不过最后更改成了统计答案
{
LL sum = 0;
int cnt = 0;
for (int i = mid + 1; i <= n; i++) //不同点
if (choose[i] == 1)
{
if (sum + a[i] > s) return;
sum += a[i];
}
else if (choose[i] == 2)
{
LL ai = fac(a[i]);
if (ai == -114514 || cnt + 1 > k || sum + ai > s) return;
sum += ai, cnt++;
}
if (vis.count(s - sum)) ans += calc(k - cnt, s - sum); //不同点
}
最后输出答案即可。
时间复杂度大约是
可见
重点是它和普通爆搜很相似,要多处理一点东西罢了。这也是它在 OI 界小有名气的原因。
完整代码
link。
希望能帮助到大家!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通