赛马场上的术士

前言

在同一个OJ,同样的C++11,几份代码对比如下:

std \(O(Tn^2\log_2n)\) 5634ms,46.46MB.

OneInDark 对题解做法进行优化,\(O(Tn\log_2^2n)\) 130ms,520KB.

\(O(Tn^2)\) 115ms,2656KB.

如果发现我的做法有问题,欢迎 Hack。

好家伙,不到一个小时就被 Hack 了,锅已经修了。

好,又被 Hack 了,贪心思路应该没什么问题,具体实现我锅了,现在又修好了。(只改了出锅的L,等E出锅了再改

代码已经改得面目全非了QAQ

题目

\(\tt 3s\ 256MB\)

小P找到了 \(\tt T\) 个全是术士的赛马场,由于对术士的马速深恶痛绝,所以他在门口装了一个监控以监视术士的行踪,最初赛马场内部的情况是未知的。

今天小P监视到了术士的 \(\tt N\) 个行动,可以表示为:

  • \(\tt E\ ID\),表示编号为 ID 的术士进入了赛马场;当 \(\tt ID\)\(0\) 时,表示一个术士伪装后进入了赛马场。
  • \(\tt L\ ID\),表示编号为 ID 的术士离开了赛马场;当 \(\tt ID\)\(0\) 时,表示一个术士伪装后离开了赛马场。

小P想知道赛马场是否一定有其他出入口。

如果有,输出 OTHER,如果没有的话,小P想知道一天结束后赛马场中最少可能会有多少术士。

以便小P在此时进入赛马场而承担最少的风险。(如果你不知道炉石传说赛马场上的集结,你可以忽略这句话)

\(1\le \tt N\le 1000;1\le \tt T\le 10;0\le \tt ID\le 2000.\)

你可以结合样例来更好地理解题意:

样例输入

2
3
E 5
L 0
E 5
2
L 1
L 1

样例输出

1
OTHER

讲解

这是一个复杂度比标程优,和标程拍了 \(7000+\) 组无锅的做法。

为了方便,我们把赛马场存在多个出入口称为无解。

无解情况

首先我们考虑可能出现无解的情况有且仅有两种:一个人连续进入或连续离开赛马场两次。

对于一个 \(\tt ID_i>0\) 的操作,我们找到上一个 \(\tt ID_j=ID_i,j<i\) 的位置。

也就是说 \(\tt j,i\) 是两个相邻的且 \(\tt ID\) 相同的数字。

  • \(\tt E\ E\) 情况,我们需要在中间找一个 \(\tt L\),因为 \(\tt j,i\) 是两个相邻的且对应 \(\tt ID\) 相同的位置,所以此时找到的 \(\tt L\) 对应的 \(\tt ID\) 一定是 \(0\),因此找的 \(\tt L\) 越靠前越好,找不到即无解。
  • \(\tt L\ L\) 情况,我们需要在中间找一个 \(\tt E\),同理其对应的 \(\tt ID\) 也一定是 \(0\),所以找的 \(\tt E\) 越靠后越好,找不到即无解。
  • \(\tt E\ L\) 情况,我们对 \(\tt L\) 打上一个标记,表示其可以匹配前面的一个 \(\tt E\),注意不是直接匹配
  • \(\tt L\ E\) 情况,跳过。

贪心匹配

无解情况 中,如果我们发现是有解的,其中的操作相当于把所有必须匹配的 \(\tt E\)\(\tt L\) 匹配上了,接下来我们要做的就是尽可能多的匹配 \(\tt E\ L\)

因为对答案有贡献的是未被匹配的 \(\tt E\),所以我们枚举的应该是 \(\tt E\),而且不难发现我们需要从后往前枚举。

此时我们分情况讨论优先级,下文的 \(\tt x,y\) 表示某个非 \(0\) 数。

\(\tt E\ 0\) 情况:

任何 \(\tt L\) 都可以和它匹配,而 \(\tt L\ 0\) 是万能的,可以和任何 \(\tt E\) 匹配,所以我们尽可能多的留下 \(\tt L\ 0\),于是我们优先找 \(\tt L\ x\) 与其匹配,但 \(\tt L\ x\) 中也有优先级问题,还记得我们之前打的标记吗,我们没有打上标记的 \(\tt L\ y\) 的前面找不到一个对应的 \(\tt E\ y\) 与其匹配,只能匹配 \(\tt E\ 0\)

所以此时的匹配优先级为: 没有标记的 \(\tt L\ y\) ,有标记的 \(\tt L\ x\),最后是 \(\tt L\ 0\)

\(\tt E\ x\) 情况:

此时只有对应的 \(\tt L\ x\) 和万能的 \(\tt L\ 0\) 能与其匹配。

显然优先级为:\(\tt L\ x\),然后再找 \(\tt L\ 0\)

代码

贴心注释版。
//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 2005;
int n,a[MAXN],dn[MAXN],ID[MAXN];
bool mat[MAXN];
char c[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

bool vis[MAXN][MAXN];
bool solve(int x)
{
	int i = dn[x];
	if(vis[i][x]) return 0;
	vis[i][x] = 1;
	for(int j = x-1;j >= ID[i];-- j)
	{
		if(vis[i][j]) return 0;
		if(c[j] == 'E' && !a[j] && !dn[j])
		{
			dn[j] = dn[i] = i;
			return 1;
		}
	}
	for(int j = x-1;j >= ID[i];-- j)
		if(c[j] == 'E' && !a[j] && dn[j])
		{
			if(solve(j))
			{
				dn[j] = dn[i] = i;
				return 1;
			}
		}
	return 0;
}

int main()
{
//	freopen("probe.in","r",stdin);
//	freopen("probe.out","w",stdout);
	for(int T = Read(); T ;-- T)
	{
		n = Read();
		for(int i = 1;i <= n;++ i)
		{
			c[i] = getchar();
			while(c[i] != 'E' && c[i] != 'L') c[i] = getchar();
			a[i] = Read(); 
			dn[i] = mat[i] = 0;
			ID[i] = 0;
		}
		for(int i = 1;i <= n;++ i)
			for(int j = i;j <= n;++ j)
				vis[i][j] = 0;
		bool ots = 0;
		for(int i = 1;i <= n;++ i)
		{
			if(!a[i]) continue;
			for(int j = i-1;j >= 1 && !ID[i];-- j)
				if(a[j] == a[i])
					ID[i] = j;
			if(!ID[i]) continue;
			if(c[i] == 'E')//E 
			{
				if(c[ID[i]] == 'E')//E E 找尽量前面的L 0 
					for(int j = ID[i];j <= i;++ j)
					{
						if(c[j] == 'L' && !a[j] && !dn[j])
						{
							dn[j] = dn[ID[i]] = i;
							break;
						}
						if(j == i) ots = 1;
					}
				if(ots) break;
			}
			else //L
			{
				if(c[ID[i]] == 'L')//L L 找尽量后面的E 0 
				{
					for(int j = i;j >= ID[i];-- j)
					{
						if(c[j] == 'E' && !a[j] && !dn[j])
						{
							dn[j] = dn[i] = i;
							break;
						}
					}
					if(dn[i]) continue;
					for(int j = i;j >= ID[i];-- j)
					{
						if(c[j] == 'E' && !a[j] && solve(j))
						{
							dn[j] = dn[i] = i;
							break;
						}
						if(j == ID[i]) ots = 1;
					}
					if(ots) break;
				}
				else mat[ID[i]] = mat[i] = 1;//E x -> L x,打标记 
			}
		}
		if(ots) {printf("OTHER\n");continue;}
		for(int i = 1;i <= n;++ i) if(dn[i]) a[i] = -1;
		int ans = 0;
		for(int i = n;i >= 1;-- i)
		{
			if(a[i] < 0) continue;
			if(c[i] == 'E') //下面进入复读机模式
			{
				if(!a[i])//E 0
					for(int j = n;j >= i && a[i] >= 0;-- j)
						if(a[j] > 0 && c[j] == 'L' && !mat[j])//优先找无标记的
							a[j] = a[i] = -1;
				if(!a[i])
					for(int j = n;j >= i && a[i] >= 0;-- j)
						if(a[j] > 0 && c[j] == 'L')//其次找有标记的
							a[j] = a[i] = -1;
				if(a[i])//E x
					for(int j = n;j >= i && a[i] >= 0;-- j)
						if(a[j] == a[i] && c[j] == 'L')//优先找 L x
							a[j] = a[i] = -1;
				for(int j = n;j >= i && a[i] >= 0;-- j)//实在不行只能由万能的 L 0
					if(!a[j] && c[j] == 'L')
						a[j] = a[i] = -1;
				if(a[i] < 0) continue;
				++ans;
			}
		}
		Put(ans,'\n');
	}
	return 0;
}

后记

如果你对 \(\tt E\ x\) 不一定和 \(\tt L\ x\) 优先匹配存疑,不妨看看这组数据:

输入:

1
4
E 1
L 0
E 0
L 1

输出:

0

其实我的匹配过程可以再优化。

posted @ 2021-09-10 08:58  皮皮刘  阅读(58)  评论(0编辑  收藏  举报