首页 > 其他分享 >【题解】Harbour.Space Scholarship Contest 2023-2024 D,E,F(CF1864)

【题解】Harbour.Space Scholarship Contest 2023-2024 D,E,F(CF1864)

时间:2023-09-01 18:45:26浏览次数:43  
标签:1000010 CF1864 Contest int 题解 Alice tot -- 反转

D.Matrix Cascade

题目描述:

有一个大小为\(n \times n\)的矩阵,由 0 和 1 组成。行的编号从上到下依次为\(1\)到\(n\),列的编号从左到右依次为\(1\)到\(n\)。第\(x\)行与第\(y\)列交叉处的单元格记为\((x, y)\)。

水月想把矩阵的所有元素都变成 0。她可以一步完成以下操作:

  • 选择任意一个单元格,假设它是\((i, j)\),然后反转\((i, j)\)中的元素,并反转\((x, y)\)单元格中的所有元素,要求 \(x > i\) 且 \(x-i \ge \left| y - j\right|\)。将一个值反转意味着将它变为相反的值:0 变为 1,1 变为 0。

帮助 AquaMoon 确定将矩阵中的所有元素变为 0 所需的最少步骤数。我们可以证明答案总是存在的。
\(2 \le n \le 3000\)

题目分析:

首先肯定是观察一次操作是在干什么:将一个顶点为 \((i,j)\) 的下三角形反转。
那么怎么反转最优呢,一个经典的思考方式就是矩阵第 \(1\) 行的 \(1\) 怎么反转,显然只有对 \((1,x)\) 进行一次操作才行。
这样我们就搞完了第一行,然后就考虑第二行,就从上到下依次考虑每一行,若当前位置为 \(1\) 则反转。
下面的问题就是如何维护当前位置的值是什么,如果每次操作都暴力做的话就是 \(O(n^3)\),而且好像也没什么可以直接维护的方式。
那么我们就考虑不直接做,而是对于每一个位置维护这个位置被反转了几次,可以发现有贡献的点就是对应着一个以 \((i,j)\) 为顶点的上三角形。
我们可以设 \(f_{i,j}\) 表示 \((i,j)\) 被反转的次数,则有 \(f_{i,j} = f_{i-1,j} + val1_{i+j} + val2_{i-j}\)。
这个转移具体来说就是考虑从上一行到这一行新增的有贡献的点是哪些,就是 \((i,j)\) 所在的两条对角线上的操作,而这两条对角线一条满足 \(i+j\) 为定值,另一条满足 \(i-j\) 为定值,所以可以直接根据这个统计。
而当我们要操作点 \((i,j)\) 时就是将 \(f_{i,j} + 1 \to f_{i,j},val1_{i+j} + 1 \to val1_{i+j},val2_{i-j} + 1 \to val2_{i-j}\) 即可。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3005;
int tmp[N][N];
char s[N][N];
map<int,int> mp1,mp2;
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%d",&T);
	while(T--){
		int n;scanf("%d",&n);
		for(int i=1; i<=n; i++)	scanf("%s",s[i]+1);
		int ans = 0;
		for(int i=1; i<=n; i++){
			for(int j=1; j<=n; j++){
				tmp[i][j] = tmp[i-1][j] + mp1[i+j] + mp2[i-j];
				if((tmp[i][j] & 1) != ((s[i][j] - '0') & 1)){
					++ans;
					mp1[i+j]++,mp2[i-j]++;
					tmp[i][j]++;
				}
			}
		}
		printf("%d\n",ans);
		mp1.clear(),mp2.clear();
		for(int i=1; i<=n; i++){
			for(int j=1; j<=n; j++){
				tmp[i][j] = 0;
			}
		}
	}
	return 0;
}

E.Guess Game

题目描述:

卡罗尔有一个由 \(n\)个非负整数组成的序列 \(s\)。她想与 Alice 和 Bob 玩 "猜谜游戏"。

为了玩这个游戏,卡罗尔将在范围 \([1, n]\)内随机选择两个整数指数 \(i_a\) 和 \(i_b\),并设置 \(a=s_{i_a}\), \(b=s_{i_b}\)。请注意,\(i_a\)和\(i_b\)可能重合。

卡罗尔会进行如下步骤:

  • 将\(a\)的值告诉 Alice;
  • 将\(b\)的值告诉 Bob;
  • 同时告诉 Alice 和 Bob \(a \mid b\)的值,其中 \(|\)表示 bitwise OR 运算

请注意,卡罗尔不会告诉 Alice 或 Bob 任何关于\(s\)的信息。

然后开始猜谜。两名玩家轮流猜测,Alice 先开始。双方的目标都是确定以下哪项为真 \(a < b\)、\(a > b\)或\(a = b\)。

在自己的回合中,玩家可以做以下两件事之一:

  • 说 "我不知道",并将回合传给另一名玩家;
  • 说 "我知道",然后回答"\(a<b\)"、"\(a>b\)"或"\(a=b\)";然后游戏结束。

Alice 和 Bob 能听到对方说的话,并能利用这些信息推断出答案。Alice 和 Bob 都很聪明,只有在完全确定时才会说 "我知道"。

你需要计算玩家在游戏中回合数的期望值。输出答案,取模 \(998\,244\,353\)。

形式上,让 \(M = 998\,244\,353\)。可以证明答案可以表示为不可约分数 \(\frac{p}{q}\),其中 \(p\)和 \(q\)是整数,而 \(q \not \equiv 0 \pmod{M}\)是不可约分数。输出等于 \(p \cdot q^{-1} \bmod M\)的整数。换句话说,输出这样一个整数 \(x\),即 \(0 \le x < M\) 和 \(x \cdot q \equiv p \pmod{M}\)。

题目分析:

首先期望显然可以转化为总代价除以总方案数。
总方案数是很好算的,就是 \(n \times n\),就是考虑总代价怎么计算。
要计算总代价必须要做到计算:给定 \(a,b\) 得到轮数是多少,其实就是要弄明白 "我不知道" 有什么作用。
首先我们可以仅保留 \(a | b\) 中为 \(1\) 的位,因为除了这些位会出现疑问其他的都没问题了。
如果 Alice 发现 \(a\) 的最高位为 \(0\) 那么就意味着 \(b\) 的最高位一定是 \(1\) 这个时候会立刻知道 \(a < b\)。
否则 Alice 就无法确定大小关系是什么,也就是此时的 "我不知道" 相当于说明了其最高位为 \(1\)。
也就是说第 \(i\) 次回答 "我不知道" 相当于对应的人说明了其知道的数的从高到低第 \(i\) 位为 \(1\),那么最终确定大小关系肯定就是到达了某个当前位为 \(0\) 的位置。
下面就分类讨论一下,将这个轮数转化为形式化的语言,下文中默认只保留 \(a | b\) 中为 \(1\) 的位:

  • 若 \(a < b\),则设 \(a\) 中第一个 \(0\) 的位置为 \(i\),则答案为 \(i + 1 - [i\%2 = 1]\)
  • 若 \(a=b\),则设其总共有 \(k\) 位,则答案为 \(k + 1\)
  • 若 \(a > b\),则设 \(b\) 中第一个 \(0\) 的位置为 \(i\),则答案为 \(i + [i\%2 = 1]\)

这个时候统计总代价就可以考虑枚举 \(b\) 然后对于第一种贡献就是枚举 \(a\) 中第一个 \(0\) 的位置(也可以转化为枚举对应的 \(b\) 中的 \(1\) 的位置),第三种贡献就是枚举 \(b\) 中第一个 \(0\) 的位置,第二种贡献就是对于相等的特殊处理。
因为需要使用 map 所以复杂度是 \(O(n\log n \log V)\) 能过。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e5+5;
const int MOD = 998244353;
int a[N];
unordered_map<int,int> mp[32];
int power(int a ,int b){
	a %= MOD;
	int res = 1;
	while(b){
		if(b & 1)	res = res * a %MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return res;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int n;scanf("%lld",&n);
		for(int i=1; i<=n; i++)	scanf("%lld",&a[i]);
		sort(a+1,a+n+1);
		int ans = 0;
		//统计 a < b 的答案
		int tot = 0;
		for(int i=1; i<=n; i++){
			tot = 0;
			for(int j=30; j>=0; j--){
				if((a[i] >> j) & 1){
					++tot;
					ans = (ans + mp[j][(a[i] >> j) ^ 1] * (tot + 1 - (tot % 2 == 1)))%MOD;
				}
			}
			for(int j=30; j>=0; j--){
				mp[j][a[i]>>j]++;
			}
		} 
		for(int i=0; i<=30; i++)	mp[i].clear();
		
		//统计 a > b 的答案 
		for(int i=n; i>=1; i--){
			tot = 0;
			for(int j=30; j>=0; j--){
				if(!((a[i] >> j) & 1)){
					ans = (ans + mp[j][(a[i] >> j) ^ 1] * (tot + 1 + ((tot + 1) % 2 == 1)))%MOD;
				}
				else	++tot;
			}
			for(int j=30; j>=0; j--){
				mp[j][a[i]>>j]++;
			}
		}
		for(int i=0; i<=30; i++)	mp[i].clear();
				
		//统计 a = b 的答案
		for(int i=1; i<=n;){
			int pos = i;
			for(;pos <= n && a[pos] == a[i]; pos++);
			--pos;
			int tmp = __builtin_popcount(a[i]),len = pos - i + 1;
			ans = (ans + len * len %MOD * (tmp + 1) %MOD)%MOD;
			i = pos + 1;
		}
		
		printf("%lld\n",ans * power(n * n,MOD-2) %MOD);
	}
	return 0;
}

F.Exotic Queries

题目描述:

AquaMoon 给 RiverHamster 一个整数序列 \(a_1,a_2,\dots,a_n\),RiverHamster 给你\(q\)个查询。每个查询由两个整数\(l\)和\(r\)表示。

对于每个独立的查询,您可以取序列的任意连续段,然后从该段的所有数字中减去一个相同的非负值。您可以多次(可能是零次)这样做。但是,您不能选择两个互不包含的相交线段。您的目标是将初始在 \([l, r]\)范围内的所有数字转换成 \(0\)。您必须用最少的运算次数完成这项工作。

请注意,查询是独立的,数组中的数字会在两次查询之间恢复到初始值。

题目分析:

因为每次都必须要求线段相交,所以一个显然的贪心策略就是每次将最小值减成 \(0\) 然后序列就被这些 \(0\) 分割成了一个个段,然后对于每一段递归处理即可。
我们很想让同一个数在一次操作内全部被搞掉,但是有的时候并做不到,那么是什么时候做不到这件事呢。
对于一个数 \(x\) 设其出现的位置为 \(p_1,p_2,\cdots,p_m\),则对于 \(p_i,p_{i+1}\) 若有一个数将它们分割开了它们就不能在一起处理了,也就是存在 \(y \in [p_i+1,p_{i+1}-1]\) 且满足 \(a_y < x\),因为我们存在值域在 \([l,r]\) 的限制,所以这里其实限制是 \(l \le a_y < x\)。
而每次满足这个限制也就意味着我们的答案要加 \(1\)。
显然最优的一个 \(a_y\) 就是 \([p_i+1,p_{i+1}-1]\) 的最大值,考虑对于由数 \(x\) 分割出来的每一段都预处理出这一段的最大值,设其构成集合 \(S_x\)。
那么因为我们一个查询可以等价于只保留值域在 \([l,r]\) 内的数,所以一个查询的答案就是 \(S_l,S_{l+1},\cdots,S_r\) 中大于等于 \(l\) 的数的个数(设前面这个的查询为 \(T(l,r,l)\))加不同种类的数的个数。
不同种类数的个数很好做,而对于 \(T(l,r,l)\) 显然可以拆成 \(T(1,r,l) - T(1,l-1,l)\),这样就只需要按值域从小到大扫描线每次加入 \(S_x\) 即可。

代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, q, l, r, mx, a[1000010], ans[1000010], s[1000010];
struct Query{
	int x, id, op;
};
vector <Query> ask[1000010];
vector <int> pos[1000010];
struct Segment{
	#define lc(x) x<<1
	#define rc(x) x<<1|1
	int d[4000010];
	void pushup(int k){
		d[k] = max(d[lc(k)], d[rc(k)]);
	}
	void modify(int k, int l, int r, int x, int y){
		if (l == r){
			d[k] = y;
			return ;
		}
		int mid = l + r >> 1;
		if (x <= mid) modify(lc(k), l, mid, x, y);
		else modify(rc(k), mid+1, r, x, y);
		pushup(k);
	}
	int query(int k, int l, int r, int x, int y){
		if (x <= l && r <= y) return d[k];
		int mid = l + r >> 1, ret = 0;
		if (x <= mid) ret = max(ret, query(lc(k), l, mid, x, y));
		if (y > mid) ret = max(ret, query(rc(k), mid+1, r, x, y));
		return ret;
	}
}S;
struct BIT{
	int d[1000010];
	void add(int p, int x){
		for (; p<=n; p+=p&-p) d[p] += x;
	}
	int query(int p){
		int ret = 0;
		for (; p; p-=p&-p) ret += d[p];
		return ret;
	}
}T;
int main(){
	scanf ("%d%d", &n, &q);
	for (int i=1; i<=n; i++){
		scanf ("%d", &a[i]);
		pos[a[i]].push_back(i);
	}
	for (int i=1; i<=n; i++){
		if (pos[i].size()) s[i] = s[i-1] + 1;
		else s[i] = s[i-1];
	}
	for (int i=1; i<=q; i++){
		scanf ("%d%d", &l, &r);
		ans[i] += s[r] - s[l-1];
		ask[l-1].push_back((Query){l, i, -1});
		ask[r].push_back((Query){l, i, 1});
	}
	for (int i=1; i<=n; i++){
		for (int j=0; j<pos[i].size(); j++){
			if (j > 0){
				int mx = S.query(1, 1, n, pos[i][j-1], pos[i][j]);
				if (mx) T.add(mx, 1);
			}
		}
		for (int j=0; j<pos[i].size(); j++){
			S.modify(1, 1, n, pos[i][j], i);
		}
		for (int j=0; j<ask[i].size(); j++){
			int x = ask[i][j].x, id = ask[i][j].id, op = ask[i][j].op;
			ans[id] += op * (T.query(n) - T.query(x-1));
		}
	}
	for (int i=1; i<=q; i++){
		printf ("%d\n", ans[i]);
	}
	return 0;
}

标签:1000010,CF1864,Contest,int,题解,Alice,tot,--,反转
From: https://www.cnblogs.com/linyihdfj/p/17672118.html

相关文章

  • kde桌面“屏幕边缘”无法触发问题解决
    鼠标放到屏幕边缘无法触发效果,搜索后是设置问题。解决办法:系统设置——硬件——显卡与显示器——显示特效合成器,勾选允许应用阻止显示特效合成,然后右下角点击应用参考:https://www.cnblogs.com/pipci/p/14870650.html......
  • AtCoder Beginner Contest 317
    A-Potions#include<bits/stdc++.h>usingnamespacestd;#defineintlonglongintpower(intx,inty,intp){x%=p;intans=1;while(y){if(y&1)ans=ans*x%p;y>>=1,x=x*x%p;}......
  • AtCoder Beginner Contest 292 E - Transitivity
    E-Transitivity原题链接题意:对于一个有向图,进行加边操作,使最终任意的个点具有传递效果,即若a到b有边,b到c有边,那么a到c也要有边,问最少需要进行多少次操作,使得每个节点所能到达的地方都有直接的边,也就是最短距离为1思路:怎么加边才是最优的,举个例子a->b->c->d->e对于a点到其他......
  • AtCoder Beginner Contest 292 D - Unicyclic Components
    D-UnicyclicComponents原题链接题意:判断一个连通块的边和点个数是否相同思路:对它使用并查集吧点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=200010,M=N<<1;//维护连通图中点和边的个数intsd[N],se[N],p[N];boolf[N];//谁是祖宗int......
  • CF1864D 题解
    CF1864DMatrixCascade题解Links洛谷CodeforcesDescription给定一个\(n\timesn\)的01矩阵。定义一次操作为:选择矩阵上第\(i\)行第\(j\)列的格子\((i,j)\),将其取反,并取反所有满足\(x>i,x-i\ge|y-j|\)的位置\((x,y)\)。其中,“取反”的意思为:把\(0\)......
  • CF1712F Triameter 题解
    Description你有一棵有\(n\)个点的树,树上的每条边权值都为\(1\)。现在有\(q\)次询问,每次询问一个整数\(x\),并将叶子结点全部相连上权值为\(x\)的边(操作不会保留)。问每次操作后图的直径是多少。图的直径定义为\(\underset{1\lequ<v\leqn}{\max}d(u,v)\)。\(3\leqn\le......
  • springcloud 跨域问题解决
    问题原因跨域本质是浏览器基于同源策略的一种安全手段同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能所谓同源(即指在同一个域)具有以下三个相同点协议相同(protocol)主机相同(host)端口相同(port)反之非同源请求,也就是协议、端口、主机其中一项不相同的......
  • MySQL 使用Navicat delete/insert into/update 大量数据表锁死,kill的线程后线程处于ki
      MySQL使用delete/insertinto/update大量数据表锁死,kill的线程后线程处于killed状态问题解决实际生产环境问题描述:使用Navicat备份BigData数据表时不小心点到了取消按钮,导致数据表被锁。  查看MySQL线程队列,找到刚刚执行的SQL看是属于什么状态。showprocessli......
  • 选博士 题解
    FZQOJLuogu前言​ 节假日在家闲来无事,那就水写一篇题解吧。题目描述​ 前面一大堆废话,总结来说就是:​ 求l--R区间的和​ 但是每一个数转换为它本身所有数位的和,重复这个操作,直到这个数⩽9思路​ 我不知道各位神犇们是怎么做的,所以在此分享一下我的思路前缀......
  • 「USACO3.2」Magic Squarest题解
    「USACO3.2」MagicSquarest题解建议优先阅读题目后再看题解:FZQOJluogu-题目大意给定初始二维数组(也即是题中所说的魔板):12348765并提供以下3种操作:\(A\).交换上下两行;\(B\).将最右边的一行移动到最左边;\(C\).顺时针旋转魔板的中央4个数字询问最少多少次......