首页 > 其他分享 >CF525E 题解

CF525E 题解

时间:2022-10-17 13:00:33浏览次数:76  
标签:cnt return int 题解 sum ai CF525E LL

前言

题目传送门!

更好的阅读体验?

比较套路的折半搜索。代码实现略微繁琐。

思路

每个数有三个状态:不选、选 \(a_i\)、选 \(a_i !\)。

数据范围 \(n \le 26\),暗示着爆搜,但是 \(3^{26}\) 会爆炸。这时可以使用神仙搜索:\(\text{meet in the middle}\)。

折半搜的含义很显然:把序列分成两半,左右分别爆搜,再考虑合并左右答案。

首先看左边的搜索。这个很容易,不就是普通的爆搜吗!

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); 
}

\(\operatorname{CHK()}\) 的含义是将左右答案合并,计算最终答案。

和 \(\operatorname{chk()}\) 也很相似,不同点只有边界与统计答案的部分。

理解了左边就很容易理解右边。

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); //不同点
} 

最后输出答案即可。

时间复杂度大约是 \(O(3^{\frac{n}{2}} \times \log_3^\frac{n}{2})\)。由于是折半搜,时间大约是普通爆搜开根。

可见 \(\text{meet in the middle}\) 并不是玄学算法,它是有时间保证的。

重点是它和普通爆搜很相似,要多处理一点东西罢了。这也是它在 OI 界小有名气的原因。

完整代码

link

希望能帮助到大家!

标签:cnt,return,int,题解,sum,ai,CF525E,LL
From: https://www.cnblogs.com/liangbowen/p/16795772.html

相关文章

  • CF960E Alternating Tree题解:
    CF960EAlternatingTree题解:也许是第一道自己做的*2300。可简化为树上黑白染色。首先想到树形DP,如果是棵有根树,状态转移方程如下:\[{f[x][0]=f[y][1]+siz[y]*a[x]}\]......
  • P3959 [NOIP2017 提高组] 宝藏 题解
    一道不错的状压dp题。注意到本质上打通的路径会构成一棵树,因此实际上总花费就是一个点的层高(根节点层高为0)乘上其到父亲节点的边的边权。据此可以考虑一种初步的状压......
  • java问题解答
    因为子类继承自父类,会沿用父类的东西(没被覆盖的函数以及可见的成员变量等),而这些东西子类是没有的,需要先初始化父类才能被使用子类构造方法中调用父类构造方法,一个作用是......
  • 2021 CCPC 威海站 VP记录(题解)
    2021CCPC威海站VP记录(题解)题目顺序为vp时开题顺序:A-Goodbye,Ziyin!签到,连边数小于等于2的可以作为二叉树根,若有大于4的直接输出0。code:voidsolve(){ int......
  • [题解] Codeforces Global Round 23 1746 A B C D E1 F 题解
    点我看题求点赞A.Maxmina首先序列全0的情况肯定是NO。否则,如果\(k\ge3\),则在序列中随便找一个1,把他左边和右边分别用第一种操作不断缩,直到序列长度为k为止,最后用一次2......
  • CF1153F 题解
    传送门题意有一段长为\(l\)的线段,有\(n\)个区间,左右端点在\([0,l)\)间均匀随机(可能不是整数)。求期望被至少\(k\)段区间覆盖的长度,对\(998244353\)取膜模。题......
  • Codeforces Global Round 23题解
    T1link大水题,不想说最后一定可以把一个序列消成长度为\(k\)的带一序列,前提是其原来就有一所以贪心就是如果有一,就行,反之不行codeT2linkwssb,考试的时候居然想了大......
  • Codeforces Global Round 23 (A-E1)个人题解
    A-Maxmina给定一个01串,我们可以将k个数变为他们的最大值(k个数变成1个数),或者将相邻的两个数变为他们的最小值(2个数变成1个数),询问是否可以将这个01串变成仅含有一个1的......
  • Codeforces Global Round 23 题解
    ContestLink我是智障。A.MaxminaProblemLink显然当数组中全是\(0\)的时候,最后不可能变成\(1\),因为我们只有相邻取\(\min\)和区间取\(\max\)两种操作,并没有任......
  • AtCoder Beginner Contest 273 题解
    第一次AtCoder体验,不是很好。ARecursiveFunction原题链接大水题。只要会递归就会做。点击查看代码#include<cstdio>#include<iostream>#definelllonglong......