前言
在同一个OJ,同样的C++11,几份代码对比如下:
std (O(Tn^2log_2n)) 5634ms,46.46MB.
OneInDark 对题解做法进行优化,(O(Tnlog_2^2n)) 130ms,520KB.
我 (O(Tn^2)) 115ms,2656KB.
如果发现我的做法有问题,欢迎 Hack。
好家伙,不到一个小时就被 Hack 了,锅已经修了。
好,又被 Hack 了,贪心思路应该没什么问题,具体实现我锅了,现在又修好了。(只改了出锅的L,等E出锅了再改
代码已经改得面目全非了QAQ
题目
( t 3s 256MB)
小P找到了 ( t T) 个全是术士的赛马场,由于对术士的马速深恶痛绝,所以他在门口装了一个监控以监视术士的行踪,最初赛马场内部的情况是未知的。
今天小P监视到了术士的 ( t N) 个行动,可以表示为:
- ( t E ID),表示编号为 ID 的术士进入了赛马场;当 ( t ID) 为 (0) 时,表示一个术士伪装后进入了赛马场。
- ( t L ID),表示编号为 ID 的术士离开了赛马场;当 ( t ID) 为 (0) 时,表示一个术士伪装后离开了赛马场。
小P想知道赛马场是否一定有其他出入口。
如果有,输出 OTHER
,如果没有的话,小P想知道一天结束后赛马场中最少可能会有多少术士。
以便小P在此时进入赛马场而承担最少的风险。(如果你不知道炉石传说赛马场上的集结,你可以忽略这句话)
(1le t Nle 1000;1le t Tle 10;0le t IDle 2000.)
你可以结合样例来更好地理解题意:
样例输入
2
3
E 5
L 0
E 5
2
L 1
L 1
样例输出
1
OTHER
讲解
这是一个复杂度比标程优,和标程拍了 (7000+) 组无锅的做法。
为了方便,我们把赛马场存在多个出入口称为无解。
无解情况
首先我们考虑可能出现无解的情况有且仅有两种:一个人连续进入或连续离开赛马场两次。
对于一个 ( t ID_i>0) 的操作,我们找到上一个 ( t ID_j=ID_i,j<i) 的位置。
也就是说 ( t j,i) 是两个相邻的且 ( t ID) 相同的数字。
- ( t E E) 情况,我们需要在中间找一个 ( t L),因为 ( t j,i) 是两个相邻的且对应 ( t ID) 相同的位置,所以此时找到的 ( t L) 对应的 ( t ID) 一定是 (0),因此找的 ( t L) 越靠前越好,找不到即无解。
- ( t L L) 情况,我们需要在中间找一个 ( t E),同理其对应的 ( t ID) 也一定是 (0),所以找的 ( t E) 越靠后越好,找不到即无解。
- ( t E L) 情况,我们对 ( t L) 打上一个标记,表示其可以匹配前面的一个 ( t E),注意不是直接匹配!
- ( t L E) 情况,跳过。
贪心匹配
在 无解情况
中,如果我们发现是有解的,其中的操作相当于把所有必须匹配的 ( t E) 和 ( t L) 匹配上了,接下来我们要做的就是尽可能多的匹配 ( t E L)。
因为对答案有贡献的是未被匹配的 ( t E),所以我们枚举的应该是 ( t E),而且不难发现我们需要从后往前枚举。
此时我们分情况讨论优先级,下文的 ( t x,y) 表示某个非 (0) 数。
( t E 0) 情况:
任何 ( t L) 都可以和它匹配,而 ( t L 0) 是万能的,可以和任何 ( t E) 匹配,所以我们尽可能多的留下 ( t L 0),于是我们优先找 ( t L x) 与其匹配,但 ( t L x) 中也有优先级问题,还记得我们之前打的标记吗,我们没有打上标记的 ( t L y) 的前面找不到一个对应的 ( t E y) 与其匹配,只能匹配 ( t E 0)。
所以此时的匹配优先级为: 没有标记的 ( t L y) ,有标记的 ( t L x),最后是 ( t L 0)。
( t E x) 情况:
此时只有对应的 ( t L x) 和万能的 ( t L 0) 能与其匹配。
显然优先级为:( t L x),然后再找 ( t 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
");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,'
');
}
return 0;
}
后记
如果你对 ( t E x) 不一定和 ( t L x) 优先匹配存疑,不妨看看这组数据:
输入:
1
4
E 1
L 0
E 0
L 1
输出:
0
其实我的匹配过程可以再优化。