首页 > 其他分享 >ABC370 DEF 题解

ABC370 DEF 题解

时间:2024-09-10 20:37:53浏览次数:11  
标签:int 题解 sum operatorname times ABC370 col DEF row

ABC370 DEF 题解

赛时过了 ABCD,补题的时候发现 EF 其实也是简单题,为什么就做不出来呢?E 这样简单的 dp 都做不出来,dp 必须得多练啊!

D - Cross Explosion

题目链接

对于每一行、列,我们要用一个数据结构来维护未被删除的点,并且要快速找到某一行/列中是否存在某个数,以及查询某个数的前驱/后继。自然想到使用 set

或许只看第一个要求:维护未被删除的点,会想到用链表。但链表的随机访问是线性复杂度的,不能快速查询某个数的位置。

然后直接按题意模拟即可。这一步主要考察对 set 的使用,刚好我这几天一直在加强学习 STL,感觉已经逐渐熟能生巧了,代码还是比较好看的(

代码实现上的一些细节:

  1. 可以用 set.lower_bound(x) 来查询 x 是否存在。设返回的迭代器为 it,如果存在,那么 *it == x。如果不存在,那 it 就指向 x 的后继,prev(it) 指向 x 的前驱。(这里说前驱后继指的是假设 x 存在,x 的前驱后继。)

    set.find(x) 也可以查询 x 是否存在,但是如果它不存在,用 find() 不好查找前驱后继。

    以及,不要用 algorithm 库中的 lower_bound(),要用 set 自带的 lower_bound(),否则无法保证单次查找的时间复杂度是对数。

  2. 每次删除一个点,要同时在维护行和列的 set 中删除。(一开始没注意到这个调了好久)

int n, m, q, cnt;
vector<set<int>> row, col;

void del(int x, int y)
{
	cnt--;
	auto it_row = row[x].find(y);
	assert(it_row != row[x].end());
	row[x].erase(it_row);
	
	auto it_col = col[y].find(x);
	assert(it_col != col[y].end());
	col[y].erase(it_col);
}

int main()
{
	cin >> n >> m >> q;
	row.resize(n + 1), col.resize(m + 1);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++) row[i].insert(j);
	for(int i = 1; i <= m; i++)
		for(int j = 1; j <= n; j++) col[i].insert(j);
	
	cnt = n * m;
	for(int i = 1, x, y; i <= q; i++)
	{
		cin >> x >> y;
		auto it_row = row[x].lower_bound(y);
		if(*it_row == y) del(x, y);
		else
		{
			if(it_row != row[x].begin()) del(x, *(prev(it_row)));
			if(it_row!= row[x].end()) del(x, *it_row);
			
			auto it_col = col[y].lower_bound(x);
			if(it_col != col[y].begin()) del(*(prev(it_col)), y);
			if(it_col != col[y].end()) del(*it_col, y);
		}
	}
	
	cout << cnt << endl;
	
	return 0;
}

提交记录

E - Avoid K Partition

题目链接

首先要想到用 dp,但是我连这一步都没想到,哈哈哈。

dp 还是得多做啊。

设 \(f(i)\) 表示 \(A_1 \sim A_i\) 的合法划分方案数。易得转移方程:

\[f(i) = \sum_{j=0}^{i-1} [\operatorname{sum}(j+1 \dots i) \not= K] \times f(j) \]

这里注意一下初始化的问题:我们应初始化 \(f(0) = 1\)。为什么呢?可以从转移的角度考虑:\(j = 0\) 时,我们考虑 \(1\dots i\) 这一段的和。如果和不为 \(K\),我们就加上 \(f(0)\),这就相当于已知把 \(1 \dots i\) 当成一整段时,问 \(1 \dots i\) 划分的方案数——显然就是 \(1\)。

用上面这个转移方程转移是 \(O(N^2)\) 的。考虑优化。

求 \(f(i)\) 时,我们相当于是在 \(0 \le j < i\) 中寻找那些使得 \(\operatorname{sum}(j+1 \dots i) \not= K\) 的 \(j\)。用 \(\operatorname{sum}\) 表示前缀和,就相当于寻找那些使得 \(\operatorname{sum}(j) \not= \operatorname{sum}(i) - K\) 的 \(j\),然后求这些 \(f(j)\) 的和。

于是可以设 \(M(x)\) 表示当前(枚举到 \(i\) 时)所有使得 \(\operatorname{sum}(j) = x\) 的 \(f(j)\) 的和,那么转移方程可以改写成:

\[f(i) = \left(\sum_{j = 0}^{i-1} f(j) \right) - M(\operatorname{sum}(i) - K) \]

同时再维护一下 \(M\) 即可:\(M(\operatorname{sum}(i)) \gets M(\operatorname{sum}(i)) + f(i)\)。初始化 \(\forall x, M(x) = 0\)。\(M\) 可以用 map 维护。(用 map 的话,实际上可以不用初始化,因为 map 保证第一次访问 \(M(x)\) 时它的值为 \(0\)。)总时间复杂度 \(O(N \log N)\)。

f[0] = 1;
ll all = 1;
map<ll, ll> mp;
mp[0] = 1;
for(int i = 1; i <= n; i++)
{
    f[i] = ((all - mp[sum[i] - K]) % MOD + MOD) % MOD;
    all = (all + f[i]) % MOD;
    mp[sum[i]] = (mp[sum[i]] + f[i]) % MOD;
}

细节:作为 map 下标的数(这里是 sum[i] - K)是不能取模的,原因一是它不会爆 long long,二是不同的数取模的结果可能相同,可能导致冲突(我就因为这个爆了)。

提交记录

F - Cake Division

题目大意:有一个环状序列,要把它分成 \(K\) 段。设每段的权值为该段中所有数的和,你需要最大化最小的权值。可能有多种划分方案使得最小权值最大,你还需要求出在这些划分方案中,有哪些地方从未被划开过。

因为一些细节调了很久(恼)

先考虑如何求出最大的最小权值。

题目是个环,有点麻烦。先考虑序列是个链时怎么做。这时显然有一个简单的二分:设 \(x\) 表示最小值,然后可以 \(O(N)\) 地判定在每一段的和都大于 \(x\) 的情况下,能否把序列分为至少 \(K\) 段。

到了环上,一个经典的 trick 是断环成链:把原序列复制接在它自己的后面。然后,我们还是想到二分,但不能用上面的方法判定了。

设 \(f(i)\) 表示在 \(i + 1 \sim 2\times N + 1\) 范围内,第一个使得 \(\operatorname{sum}(i\dots f(i)-1) \ge x\) 的数。换句话说,如果 \(i\) 是某一段的开头,这一段至少要到 \(f(i) - 1\) 结束才能满足该段的权值不小于 \(x\),\(f(i)\) 就是这一段的下一个位置。(这里定义的 \(f(i)\) 往后挪了一位,主要是为了方便后续操作。当然不挪这一位也是可以的。)如果不存在这样的数,则 \(f(i) = +\infty\)。运用双指针的技巧,\(f(i)\) 可以在 \(O(N)\) 的时间内求出。

\(f(i)\) 的妙处在于它是可以自身复合的:\(f(f(i))\) 表示从 \(i\) 开始找两段以后的下一个位置,同理 \(f^{K}(i)\) 就表示从 \(i\) 开始找 \(K\) 段以后的下一个位置。于是,只要存在 \(1 \le i \le N\) 使得 \(f^{K}(i) \le N + i\),此时的 \(x\) 就是可行的。而 \(f^{K}(i)\) 可以通过倍增在 \(O(N \log K)\) 的时间内求出。

这里要注意一下 \(+\infty\) 的问题:实现时,\(+\infty\) 应该是一个比所有的 \(f(i)\) 都大的数,而 \(f(i) \le 2 \times N + 1\),所以 \(+\infty\) 可以设为 \(2 \times N + 2\)。另外,我们得保证 \(f(1 \dots 2\times N + 2)\) 中所有的数都有值,所以初始化 \(f(2\times N+1) = f(2 \times N + 2) = 2 \times N + 2\)。

接下来考虑第二个问题。

直接考虑并不容易。我们可以先求出:有多少个地方,断开之后能满足条件?这是容易的,沿用上文中的方法:只要 \(f^{K}(i) \le N + i\),那么 \(i\) 和 \(i-1\) 之间就可以断开,同时满足最小权值不小于 \(x\)。又因为我们总共有 \(N\) 个地方可以断开,所以用 \(N\) 减去这个数就是答案。

int n, K;
cin >> n >> K;
vector<int> a(n + 1), sum(2 * n + 1);
for(int i = 1; i <= n; i++)
{
    cin >> a[i];
    sum[i] = sum[i-1] + a[i];
}
for(int i = n + 1; i <= 2 * n; i++)
    sum[i] = sum[i-1] + a[i-n];

vector<vector<int>> f(2 * n + 3);
for(auto &vec: f) vec.resize(__lg(K) + 1);

auto check = [&](int x) -> int // 返回需要切开的切线的数量 
{
    for(int i = 1, j = 1; i <= 2 * n; i++)
    {
        while(j < 2 * n && sum[j] - sum[i - 1] < x) j++;
        if(sum[j] - sum[i-1] >= x) f[i][0] = j + 1;
        else f[i][0] = 2 * n + 2; // 相当于INF 
    }
    f[2 * n + 1][0] = 2 * n + 2;
    f[2 * n + 2][0] = 2 * n + 2;

    int t = __lg(K);
    for(int k = 1; k <= t; k++)
        for(int i = 1; i <= 2 * n + 2; i++)
            f[i][k] = f[f[i][k-1]][k-1];

    int cnt = 0; // 能作为起点的点数 
    for(int i = 1; i <= n; i++) // 枚举每个点,判断其是否能成为起点
    {
        int en = i;
        for(int j = 0; (1 << j) <= K; j++)
            if(K & (1 << j)) en = f[en][j];
        if(en <= n + i) cnt++;
    }
    return cnt;
};

i64 l = 0, r = sum[n], ans = -1;
int cnt = -1;
while(l <= r)
{
    i64 mid = (l + r) >> 1;
    int res = check(mid);
    if(res != 0) // 可行(至少有一个解) 
    {
        l = mid + 1;
        ans = mid, cnt = n - res;
    }
    else r = mid - 1;
}

cout << ans << ' ' << cnt << endl;

标签:int,题解,sum,operatorname,times,ABC370,col,DEF,row
From: https://www.cnblogs.com/dengstar/p/18407126

相关文章

  • 「NOI2021 D1T3 庆典」题解
    uoj675加强:\(\sumk\le6\times10^5\)暴力\(u\)在\(s\Rightarrowt\)路径上\(\iff\)正图上\(s\Rightarrowu\)且反图上\(u\Rightarrowt\)时间复杂度\(O((n+m)q)\)正解只关心可达性,不妨SCC缩点成DAG。注意到一个奇怪的条件:对于三座城市\(x,y,z\),若\(x\Right......
  • 题解:[USACO07DEC] Sightseeing Cows G
    洛谷P2868题目大意有个nnn个点,mmm条边的有向图,点有点权,边有边......
  • abc370
    A.略B.模拟C.贪心,顺序枚举,若\(s_i<t_i\),将\(i\)存到数组中,否则直接令\(s_i=t_i\),输出\(s\)。然后从后往前的枚举数组,依次修改并输出即可。D.一开始看错数据范围,虚空想了好久。。用用\(set\)找每一列,每一行的第一个没被摧毁的墙壁即可。E.给一个数组\(a\)......
  • 力扣474-一和零(Java详细题解)
    题目链接:474.一和零-力扣(LeetCode)前情提要:因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。最近刚学完01背包,所以现在的题解都是以01背包问题为基础再来写的。如果大家不懂01背包的话,建议可以去学一学,01背包问题可以说是背包问题的基础。如果大家感兴趣,......
  • luogu2516题解
    随机说话第一次交的时候写的版本是这个。下面在sum的计算上少了个else,遂出错。以后写的时候还是尽量简单点,主要是调试的时候好调。题目分析题目里面的公共子序列就是可以不连续可以为空的在字符串里出现顺序相同的子串。对于一个公共子序列,在找到最后一个公共的字符的时......
  • 【pom】解决jar冲突心得 之 通过解决启动报错  Caused by: java.lang.NoClassDefFoun
     解决jar冲突心得之通过解决启动报错 Causedby:java.lang.NoClassDefFoundError:Couldnotinitializeclasscom.fasterxml.jackson.databind.ObjectMapper 学思路 一般情况,出现Causedby:java.lang.NoClassDefFoundError的问题1.要么是jar没有引入pom,所以找不......
  • P4563 [JXOI2018] 守卫 题解
    P4563[JXOI2018]守卫题解不愧是九条可怜的\(\text{JXOI}\),只能说确实是道好题。假设当前我们在求\([l,r]\),我们不难发现\(r\)端点一定要放保镖,于是考虑\(r\)保镖的最大监视范围\([x,r]\)。由题意得到对于\([x,r]\)中的每个\(p_1,p_2,\cdots,p_k\),要求斜率\(t(p_i,......
  • 树上一些点的选 题解
    题意简述给你一棵\(n\)个节点以\(1\)为根的有根树,和一个整数\(m\)。对于树上每一个点\(u\),有三个权值\(X,Y,Z\)。你需要在\(u\)的祖先里(不含\(u\))中选出至少\(X\)个点,记\(S_1\)表示这些点到\(u\)的距离之和;在\(u\)的后代里(不含\(u\))中选出至少\(Y\)个点,......
  • CF717A Festival Organization 题解
    Description一个合法的串定义为:长度在\([l,r]\)之间,且只含0,1,并且不存在连续\(2\)个或更多的\(0\)。现在要选出\(k\)个长度相同的合法的串,问有几种选法,答案模\(10^9+7\)。\(1\leqk\leq200,1\leql\leqr\leq10^{18}\)。Solution容易发现答案为\(\sum_{i=l+2}^{r......
  • 【Py/Java/C++三种语言OD独家2024E卷真题】20天拿下华为OD笔试之【回溯】2024E-字符串
    可上欧弟OJ系统练习华子OD、大厂真题绿色聊天软件戳od1441了解算法冲刺训练(备注【CSDN】否则不通过)文章目录相关推荐阅读题目描述与示例题目描述输入描述输出描述示例一输入输出说明示例二输入输出说明解题思路代码pythonjavacpp时空复杂度华为OD算法/大厂面......