Codeforces Round #700 (Div. 2) A~D题解

本场链接:Codeforces Round #700 (Div. 2)

A. Yet Another String Game

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 55;
char s[N];

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		scanf("%s",s + 1);int n = strlen(s + 1);
		forn(i,1,n)
		{
			if(i & 1)	s[i] = (s[i] == 'a' ? 'b' : 'a');
			else 		s[i] = (s[i] == 'z' ? 'y' : 'z');
		}
		printf("%s
",s + 1);
	}
	return 0;
}

B. The Great Hero

思路

这个题唯一一点比较特殊的点在于:如果人无论如何都会死,事实上也不一定就是NO,因为可能出现"同归于尽"的情况.这个时候可以讨论一下:首先第一种就是所有人直接一个一个敲死都不会让人死亡,这种肯定直接是YES,那么剩下的情况就是人无论如何都会死,可以枚举一下每个怪物作为最后一个去击杀的时候是否会出现"同归于尽"的情况,具体来说可以先把所有怪物完全击杀的代价算出来做和,然后枚举每个怪物作为最后一个的时候,当前剩余的血量和怪物的代价算一下是否会同归于尽就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7;

struct Node
{
	ll a,b,cost;
	bool operator<(const Node& r)	const
	{
		return cost < r.cost;
	}
}a[N];

int main() 
{
	ios::sync_with_stdio(0);cin.tie(0);
	int T;cin >> T;
	while(T--)
	{
		ll A,B,n;cin >> A >> B >> n;
		forn(i,1,n)	cin >> a[i].a;
		forn(i,1,n)	cin >> a[i].b;
		
		forn(i,1,n)	a[i].cost = (a[i].b + A - 1) / A * a[i].a;
		ll sum = 0;forn(i,1,n)	sum += a[i].cost;
		if(B >= sum)
		{
			cout << "YES
";
			continue;
		}
		
		bool ok = 0;
		forn(i,1,n)
		{
			if(B <= sum - a[i].cost)	continue;
			ll ts = (a[i].b + A - 1) / A;
			// cout << ts << " " << B - sum + a[i].cost << endl;
			if((ts - 1) * a[i].a <= B - sum + a[i].cost)
			{
				ok = 1;
				break;
			}
		}
		if(ok)	cout << "YES
";
		else cout << "NO
";
	}
	return 0;
}

C. Searching Local Minimum

思路

有一个更巧妙的思考方式:我们事实上可以把这个问题看做是在维护一个区间([l,r])并且始终保证(l)端点的值小于他左边一个位置的值,(r)端点的值小于他右边一个位置的值.接下来如果我们能找到一个收缩这个区间到一个点的办法,也就是说一定能保证(l=r)的话,自然就找到了答案,这个收缩区间的方法可以求整个区间的中点(mid),那么现在的问题就是看(l)(r)谁应该被移动到(mid),如果直接看(mid)和两个端点的值的关系肯定是没用的,不妨看(mid)(mid+1)的大小关系,剩下的做法就是顺理成章的了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7;
int a[N];

int main() 
{
	ios::sync_with_stdio(0);cin.tie(0);
	int n;cin >> n;
	if(n == 1)
	{
		cout << "? 1" << endl;
		int x;cin >> x;
		cout << "! " << x << endl;
		return 0;
	}
	int l = 1,r = n;
	while(l < r)
	{
		int mid = l + r >> 1;
		if(!a[mid])
		{
			cout << "? " << mid << endl;
			cin >> a[mid];
		}
		if(!a[mid + 1])
		{
			cout << "? " << mid + 1 << endl;
			cin >> a[mid + 1];
		}

		if(a[mid] < a[mid + 1])	r = mid;
		else l = mid + 1;
	}
	cout << "! " << l << endl;
	return 0;
}

Painting the Array

由于线段树dp做法可以过两个就放在一起了

D1 思路

假设我们现在在考虑(a_i)加入两个序列的影响,那么在(a_i)实际的加入产生影响之前,我们只知道(a_{i-1})一定是其中一个序列的末尾元素,另外有一个序列还不知道,对于这个题来说我们可以说只关注一个序列的末尾元素,所以其中一个序列的信息已经完整的知道了,另外一个不妨做一个dp,用(f[x])表示末尾为(x)的序列的答案最大值是多少.现在这个问题可以这样处理了:首先我们知道(a_{i-1})一定是其中一个序列的末尾,其次可以用dp的方式维护另外一个序列的各种情况下的答案.

考虑(a_i)加入两个序列产生的影响:

  • 如果(a_i eq a_{i-1}),那么可以直接把(a_i)丢到(a_{i-1})那条序列的末尾,这个时候,另外一条序列里面所有答案都应该(+1).
  • 如果(i>1)的话,我们也可以把(a_i)放在另外一个序列的末尾,但是这个时候产生了一个问题:对于下一次来(a_{i+1})的时候,现在dp维护的值末尾的元素是(a_i),根据定义来说应该维护的是另一个序列才对.也就是说此时应该交换一下两个序列.这个时候应该让(f[a[i - 1]]),也就是末尾值是上一个值的答案的最大值,增加以(a_i)为末尾的另外一个序列会产生的影响.更详细的说,这里根据直觉来看的话应该是让(f[a[i]] = max{f[x] + [x eq a[i]]}),其中(x)是那条序列的末尾元素,值可以任取.符合直觉的是:让(a_i)变成新的末尾,直接更新(a_i)这个位置上的值,但是这里因为维护的对象要交换一次,所以事实上应该是修改(f[a[i - 1]] = max{f[x] + [x eq a[i]]}).
  • 然后我们可以发现粗暴维护dp的值肯定是不行的,但是他形式上的操作等同于区间加/区间取max/单点更新max.所以可以用一个线段树来维护值最后答案就是所有的最大值.

D1代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 2e5+7;
struct Node
{
	int l,r,v;
	int add;
}tr[N * 4];
int a[N];

void pushup(int u)
{
	tr[u].v = max(tr[u << 1].v,tr[u << 1 | 1].v);
}

void pushdown(int u)
{
	auto& s = tr[u];
	auto& lf = tr[u << 1];
	auto& rt = tr[u << 1 | 1];
	if(s.add)
	{
		lf.add += s.add;
		lf.v += s.add;
		
		rt.add += s.add;
		rt.v += s.add;
		
		s.add = 0;
	}
}

void build(int u,int l,int r)
{
	if(l == r)	tr[u] = {l,r,0,0};
	else
	{
		int mid = l + r >> 1;
		tr[u] = {l,r,0,0};
		build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
	}
}

void modify(int u,int l,int r,int v)
{
	if(tr[u].l >= l && tr[u].r <= r)
	{
		tr[u].v += v;
		tr[u].add += v;
		return ;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid)	modify(u << 1,l,r,v);
	if(r > mid)		modify(u << 1 | 1,l,r,v);
	pushup(u);
}

void modify(int u,int p,int v)
{
	if(tr[u].l == p && p == tr[u].r)
	{
		tr[u].v = max(tr[u].v,v);
		return ;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(p <= mid)	modify(u << 1,p,v);
	if(p > mid)		modify(u << 1 | 1,p,v);
	pushup(u);
}

int query(int u,int l,int r)
{
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].v;
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1,res = 0;
	if(l <= mid)	res = max(res,query(u << 1,l,r));
	if(r > mid)		res = max(res,query(u << 1 | 1,l,r));
	return res;
}

int main() 
{
	int n;scanf("%d",&n);
	forn(i,1,n)	scanf("%d",&a[i]);
	build(1,1,n);
	
	forn(i,1,n)
	{
		int rt = max({query(1,1,a[i] - 1) + 1,query(1,a[i] + 1,n) + 1,query(1,a[i],a[i])});
		if(a[i] != a[i - 1])	modify(1,1,n,1);
		if(i > 1)	modify(1,a[i - 1],rt);
	}
	
	printf("%d",tr[1].v);
	return 0;
}

D2

有了上面的步骤,D2也是很自然的把max换成min就可以了.但是没这么直接,还有一点小问题:首先D1里面讨论的时候事实上忽略了初值的问题,也就是(f[0]=0),这个在D1里面求最大值是不会有区别的,因为除了第一次不会有任何转移需要(0)位的元素,但是在D2里面求最小值是有可能的,而线段树的下标是从(1)开始的,所以我的代码里面把所有的位置全体往后挪动了一位,用(1)位的值表示(0)位.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 2e5+7,INF = 1e9;
struct Node
{
	int l,r,v;
	int add;
}tr[N * 4];
int a[N];

void pushup(int u)
{
	tr[u].v = min(tr[u << 1].v,tr[u << 1 | 1].v);
}

void pushdown(int u)
{
	auto& s = tr[u];
	auto& lf = tr[u << 1];
	auto& rt = tr[u << 1 | 1];
	if(s.add)
	{
		lf.add += s.add;
		lf.v += s.add;
		
		rt.add += s.add;
		rt.v += s.add;
		
		s.add = 0;
	}
}

void build(int u,int l,int r)
{
	if(l == r)	tr[u] = {l,r,INF,0};
	else
	{
		int mid = l + r >> 1;
		tr[u] = {l,r,INF,0};
		build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
	}
}

void modify(int u,int l,int r,int v)
{
	if(tr[u].l >= l && tr[u].r <= r)
	{
		tr[u].v += v;
		tr[u].add += v;
		return ;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid)	modify(u << 1,l,r,v);
	if(r > mid)		modify(u << 1 | 1,l,r,v);
	pushup(u);
}

void modify(int u,int p,int v)
{
	if(tr[u].l == p && p == tr[u].r)
	{
		tr[u].v = min(tr[u].v,v);
		return ;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(p <= mid)	modify(u << 1,p,v);
	if(p > mid)		modify(u << 1 | 1,p,v);
	pushup(u);
}

int query(int u,int l,int r)
{
	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].v;
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1,res = INF;
	if(l <= mid)	res = min(res,query(u << 1,l,r));
	if(r > mid)		res = min(res,query(u << 1 | 1,l,r));
	return res;
}

int main() 
{
	int n;scanf("%d",&n);
	forn(i,1,n)	scanf("%d",&a[i]);
	build(1,1,n + 1);
	
	modify(1,1,0);
	forn(i,1,n)
	{
		int rt = min({query(1,1,a[i]) + 1,query(1,a[i] + 2,n + 1) + 1,query(1,a[i] + 1,a[i] + 1)});
		if(a[i] != a[i - 1])	modify(1,1,n + 1,1);
		if(i > 1)	modify(1,a[i - 1] + 1,rt);
	}
	
	printf("%d",tr[1].v);
	return 0;
}
原文地址:https://www.cnblogs.com/HotPants/p/14389813.html