【题解】Educational Codeforces Round 150(CF1841)

赛时过了 A-E,然后就开摆了,为什么感觉 C 那么无厘头[发怒][发怒]
排名:25th

A.Game with Board

题目描述:

Alice 和 Bob 玩游戏,他们有一块黑板。最初,有 n 个整数 1。Alice 和 Bob 轮流操作,Alice 先手。

轮到时,玩家必须在棋盘上选择几个(至少两个)相等的整数,擦除它们,然后写一个等于它们总和的新整数。

如果玩家不能移动(棋盘上的所有整数都不同),该玩家将赢得游戏。
如果两名选手都发挥最佳,则确定谁获胜。

题目分析:

可以发现当 n>4 时,Alice 可以一步让其变成 n2,1,1 这三个数,且 Bob 下一步只可以将两个 1 变成 2,局面变成 n2,2 Alice 就必胜。
对于 n4 的情况,手动模拟发现 Bob 必胜。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	int T;scanf("%d",&T);
	while(T--){
		int n;scanf("%d",&n);
		if(n == 2 || n == 3 || n == 4)	printf("Bob\n");
		else	printf("Alice\n");
	}
	return 0;
}

B.Keep it Beautiful

题目描述:

定义一个序列是漂亮的,当且仅当存在 k[0,n1] 使得 ak+1,ak+1,,an,a1,a2,,ak 单调不降
特别的如果序列长度为 01 则它也是漂亮的。
初始时给定序列为空,给定 q 次操作每次在序列末尾插入数 x,若插入之后序列仍为漂亮的则插入这个数,否则不插入这个数。
判断每一次操作是否可能插入这个数。
多测 t104,q2·105

题目分析:

如果我们序列就是单调不降的当然没必要断开,那么如果断开意味着我们断的肯定是单调下降的位置,因为这样才可能使得满足单调不降的条件。
所以如果存在两个位置单调下降则不合法,或者存在一个位置单调下降且首尾拼起来的位置单调下降则不合法。
所以就扫一遍判就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N],b[N];
int main(){
	int T;scanf("%d",&T);
	while(T--){
		int n;scanf("%d",&n);
		for(int i=1; i<=n; i++)	scanf("%d",&a[i]);
		int tot = 0,tmp = 0;
		for(int i=1; i<=n; i++){
			if(tot == 0)	b[++tot] = a[i],printf("1");
			else{
				if(tmp + (a[i] < b[tot]) + (b[1] < a[i]) > 1)	printf("0");
				else	tmp = tmp + (a[i] < b[tot]),b[++tot] = a[i],printf("1");
			}
		}
		printf("\n");
	}
	return 0;
}

C.Ranom Numbers

题目描述:

Ranom Number 是一个字符串,这个字符串只含字母 AEA 的值是 1B 的值是 10C 的值是 100D 的值是 1000E 的值是 10000

这个串的值按如下规则计算:如果一个字母的右侧没有值严格大于它的字母,那么它对串的值贡献为正的该字母的值,否则贡献为负的该字母的值。一个串的值就是把所有字母的贡献加起来。

例如,DAAABDCA 的值是 100011110+1000+100+1=2088

现在,给定一个 Ranom Number,你可以把它的不超过一个的字符改为其它的 AE 之间的字符,求你能得到的新 Ranom Number 的值最大可能是多少。

多组数据,输入串的总长度不超过 2×105

题目分析:

看到这个就特别想贪心,那么考虑如果选择了位置 p 那么是否存在一个位置显然比这个优呢。
如果我们要将字符变大,则显然就是想要让贡献不变的情况下产生的代价更小,也就是在相同的字符中的最靠左的一个,因为如果选择更靠右的,代价一定不会更小。
如果我们要将字符变小,同理应选择相同字符中最靠右的,因为即使我们将靠左的变小了,右边依然有同样大的,不会产生任何贡献。
所以能选择最多 10 个位置,就枚举一下就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
const int INF = 1e17;
char s[N];
int n,val[10] = {1,10,100,1000,10000};
map<char,int> vis;
int get(){
	int ans = 0;
	char mx = 'A';
	for(int i=n; i>=1; i--){
		if(mx > s[i])	ans -= val[s[i]-'A'];
		else	ans += val[s[i]-'A'],mx = s[i];
	}
	return ans;
}
int solve(int pos){
	char pre = s[pos];
	int ans = -INF;
	for(char i='A'; i<='E'; i++){
		s[pos] = i;
		ans = max(ans,get());
	}
	s[pos] = pre; 
	return ans;
}
signed main(){
	int T;scanf("%lld",&T);
	while(T--){
		scanf("%s",s+1);
		n = strlen(s+1);
		int ans = -INF;
		for(int i=1; i<=n; i++){
			if(!vis[s[i]]){
				vis[s[i]] = true;
				ans = max(ans,solve(i));
			}
		}
		vis.clear();
		for(int i=n; i>=1; i--){
			if(!vis[s[i]]){
				vis[s[i]] = true;
				ans = max(ans,solve(i));
			}
		}
		printf("%lld\n",ans);
		vis.clear();
	}
	return 0;
}

D.Pairs of Segments

题目描述:

给定 n 个线段 [li,ri],你需要从中删除尽可能少的线段,满足:

  • 剩余线段数量是偶数。
  • 剩余的线段可以两两配对,满足:
    • 属于同一对的两个线段有交;
    • 任意不属于同一对的两个线段均无交。

请你求出最少删除多少个线段才能满足要求。

多组数据,n 之和不超过 20000liri109

题目分析:

删除一些线段肯定不如转化为选择线段好做。
显然考虑贪心,但是贪了一会发现其实贪心根本做不了,所以就考虑 dp
看到这个数据范围支持 O(n2)dp,所以就设 f[i] 表示考虑了前 i 个第 i 个必须匹配的最大匹配数量。
转移就是枚举前 i 个中与它相交的设为 j,然后难道可以直接从 f[j1] 转移吗?
发现其实不行啊,因为我们转移的位置必须满足它的右端点小于 i,j 的左端点,所以就考虑将所有的区间按右端点排序,因为这样能满足我们转移的位置一定在 i 之前被得到了。
那么直接离散化用树状数组维护一下以每个位置为右端点的最优答案直接询问区间最值然后转移就好了。

下面讲一下官解,感觉比较震撼。
考虑对于一对有交的线段我们记录下他们的并集,这样一个并集就代表了一对匹配,而两个并集不相交显然就是不属于同一对的线段无交,因此我们只需要在并集的集合里求一个最大不相交区间数就可以了。
这个问题就是个经典的问题,按右端点排序后直接贪心就好了,其实这种做法和 dp 本质相同。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5;
struct node{
	int l,r;
}a[N];
int tot,mx[N],b[N],f[N];
bool cmp(node a,node b){
	return a.r < b.r;
}
int lowbit(int x){
	return x & (-x);
}
void insert(int x,int val){
	for(;x <= tot; x+=lowbit(x)){
		mx[x] = max(mx[x],val);
	}
}
int query(int x){
	int ans = 0;
	for(;x; x-=lowbit(x)){
		ans = max(ans,mx[x]);
	}
	return ans;
}
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("%d%d",&a[i].l,&a[i].r),b[++tot] = a[i].l,b[++tot] = a[i].r;
		sort(b+1,b+tot+1);tot = unique(b+1,b+tot+1) - b - 1;
		for(int i=1; i<=n; i++){
			a[i].l = lower_bound(b+1,b+tot+1,a[i].l) - b;
			a[i].r = lower_bound(b+1,b+tot+1,a[i].r) - b;
		}
		sort(a+1,a+n+1,cmp);
		for(int i=1; i<=n; i++){
			for(int j=1; j<i; j++){
				if(a[j].r >= a[i].l){ 
					f[i] = max(f[i],query(min(a[j].l,a[i].l)-1) + 2);
				}
			}
			insert(a[i].r,f[i]);
		}
		int ans = 0;
		for(int i=1; i<=n; i++)	ans = max(ans,f[i]);
		printf("%d\n",n - ans);
		for(int i=1; i<=tot; i++)	mx[i] = 0;
		for(int i=1; i<=n; i++)	f[i] = 0;
	}
	return 0;
}

E.Fill the Matrix

题目描述:

有一个 nn 列的矩阵,行和列都从 1n 编号。对于第 i ,第 1ai 个格子是黑色的,其余格子是白色的。

你可以把 1mm 个整数放到矩阵里,满足两条规则:

  1. 每个格子包含至多一个整数。
  2. 黑色格子不能包含整数。

一个矩阵的美丽程度是满足这样的格子个数:该格子自身包含一个数 j,且它右边与它相邻的格子包含的数为 j+1

请求出矩阵的最大美丽值。

多组数据,n 之和不超过 2×105,请注意 m 的大小可能超过 32 位整数所存储的范围。

题目分析:

考虑随便画一个图来理解这个局势是个什么样子:

考虑如果我们可以完全知道这个局面的所有信息那么美丽程度该怎么计算呢?
我们一定是每次选择一个长度最长的极长的白色格子段然后全部按顺序填数,直到能填的数不够了。
因为我们每多填一个区间这个区间最右边的这个数相当于浪费掉了,所以要尽可能少地浪费就是每次选尽可能长的区间。
但是上述做法就意味着我们必须知道 cnt[i] 表示长度为 i 的极长白色段的个数,而且 cnt[i]O(n2) 的,所以如果直接暴力扫描线维护就直接炸了。
所以考虑尽可能减少多余的统计,也就是我们直接暴力维护每一个极长的白色段,然后找出它在什么位置会被断开,所以从当前位置到断开的位置它都是这么长,而断开之后就直接暴力维护断开之后的两个白色段就好了,从什么位置断开其实就是区间最大值的位置,需要注意的是如果有多个区间最大值我们也没必须要一次全部弄完因为这样复杂度就太高了,可以直接维护最靠左的区间最大值的位置,然后一个个去断就好了。
会发现这样复杂度就是很对的,因为对于每一个 ai 只会导致一个极长的白色区间断开,而每次询问它的复杂度为 O(logn) 所以总复杂度就是 O(nlogn)

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
const int INF = 1e18+5;
struct node{
	int mx,pos;
}t[4 * N];
int cnt[N],a[N],n;
node operator + (node a,node b){
	node c;
	if(a.mx > b.mx)	c = a;
	else if(b.mx > a.mx)	c = b;
	else{
		c.mx = a.mx;
		c.pos = min(a.pos,b.pos);
	}
	return c;
}
void pushup(int now){
	t[now] = t[now<<1] + t[now<<1|1];
}
node query(int now,int now_l,int now_r,int l,int r){
	if(l <= now_l && now_r <= r)	return t[now];
	int mid = (now_l + now_r)>>1;
	node ans = {-INF,INF};
	if(l <= mid)	ans = ans + query(now<<1,now_l,mid,l,r);
	if(r > mid)		ans = ans + query(now<<1|1,mid+1,now_r,l,r);
	return ans;
}
void build(int now,int now_l,int now_r){
	if(now_l == now_r){
		t[now].mx = a[now_l];
		t[now].pos = now_l;
		return;
	}
	int mid = (now_l + now_r)>>1;
	build(now<<1,now_l,mid);build(now<<1|1,mid+1,now_r);
	pushup(now);
}
void solve(int l,int r,int pos){
	if(l > r || pos <= 0)	return;
	node tmp = query(1,1,n,l,r);
	cnt[r-l+1] += pos - a[tmp.pos];
	solve(l,tmp.pos-1,a[tmp.pos]);
	solve(tmp.pos+1,r,a[tmp.pos]);
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		for(int i=1; i<=n; i++)	scanf("%lld",&a[i]);
		build(1,1,n);
		solve(1,n,n);
		int m;scanf("%lld",&m);
		int ans = 0;
		for(int i=n; i>=1; i--){
			if(cnt[i] * i <= m){
				m -= cnt[i] * i;
				ans += cnt[i] * (i-1);
			}
			else{
				ans += (m / i) * (i - 1);
				m %= i;
				ans += max(0ll,m - 1);
				m = 0;
				break;
			}
		}
		printf("%lld\n",ans);
		for(int i=1; i<=n; i++)	cnt[i] = 0;
	}
	return 0;
}

F.Monocarp and a Strategic Game

题目描述:

Monocarp 玩一个策略游戏,在游戏中他开发了一个城市。这个城市有四种不同种族的生物——人类、精灵、兽人和矮人。

城市中的每个居民都有一个幸福值,它是一个整数,取决于城市中不同种族的生物数量。具体来说,每个居民的幸福值默认为 0;对于同一个种族的每个其他生物,它会增加 1;对于每个敌对种族的每个其他生物,它会减少 1。人类对于兽人有敌意(反之亦然),精灵对于矮人有敌意(反之亦然)。

游戏开始时,Monocarp 的城市没有居民。在游戏中,n 组生物会来到他的城市并希望在那里定居。第 i 组包含 ai 个人类,bi 个兽人,ci 个精灵和 di 个矮人。每次,Monocarp 可以接受或拒绝将整个生物群体加入城市。

游戏根据以下公式计算 Monocarp 的得分:m+k,其中 m 是城市中的居民数量,而 k 是城市中所有生物的幸福值之和。

帮助 Monocarp 通过最大化得分来获得游戏的胜利!
n3·105,0ai,bi,ci,di109

题目分析:

动态维护这个贡献大概是没啥想法,考虑如果现在四个种族的数量分别为 A,B,C,D (AB敌对,CD敌对)那么幸福值是多少:

A+B+C+D+A(A1)AB+B(B1)AB+C(C1)CD+D(D1)CD

化简一下就是:

(AB)2+(CD)2

考虑令一个群体为一个向量 (aibi,cidi),那么幸福值之和就是对应的向量加和的模长的平方。
考虑闵可夫斯基和,也就是每一个向量围绕着凸包转一圈形成新的凸包,最优答案一定就是这个凸包上的某一个点。
所以直接做就好了。

posted @   linyihdfj  阅读(146)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示