前言
这道题是lzw神犇自己出的一道原创好题,一开始我想到了正确解法,就是细节上没有处理好,初测只有10分,WA了9个点。
题目描述
n个珠子排成一排,每个珠子有一个颜色,用小写字母a…z表示。对于某个珠子,如果存在一个和它相邻并且颜色不同的珠子,那么称它是特殊的。每回合会消去所有特殊的珠子。问多少回合后不能再消。
输入格式
第一行n。第二行一个a…z的字符串,表示珠子的颜色。
输出格式
一行,答案。
输入输出样例
样例一:
game.in
4
aabb
game.out
2
样例二:
game.in
6
aabcaa
game.out
1
样例解释
对于样例一的两个回合:aabb->ab->NULL
对于样例二的一个回合:aabcaa->aa
数据规模与约定
50%的数据n<=10^3
100%的数据n<=10^6
题解
这道题O(n^2)的暴力很好想,就是模拟去删点,这里就不赘述了。正解是用链表做的。我们可以把所有点分成一个个块,块内所有点的颜色相同,相邻的块颜色不同。因为要快速删块,所以考虑使用链表。我们可以用node类型储存每个块的颜色和点数,并构建一个链表。每次把头和尾的块点数-1,中间的点数-2。做的时候可能有些块会被减光,就要把它前面没被减光的块和它后面没有减光的块连一条链,如果颜色相同则需要合并。至于复杂度,每个点都要被删一次,所以是O(n)的。
好久没写过链表了,有好多细节处理得还不大好,所以初测只有10分。附上我10分代码。
#include<iostream>
#include<cstdio>
using namespace std;
struct node
{
char data;
int count;
node *next;
};
int n,ans;
string s;
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
cin>>n>>s;
node *h,*p,*q;
h=new node;
h->next=NULL;
h->data=s[0];
int i=1;
while (s[i]==s[0]&&i<s.size()) i++;
h->count=i;
p=h;
while (i<s.size())
{
q=new node;
q->data=s[i];
q->next=NULL;
int x=i;
while (s[i]==q->data&&i<s.size()) i++;
q->count=i-x;
p->next=q;
p=q;
}
while (h!=NULL&&h->next!=NULL)
{
h->count--;
p=h->next;
q=h;
while (p->next!=NULL)
{
p->count-=2;
if (p->count<=0)
{
if (p->next->data==q->data)
{
q->count+=max(0,p->next->count-2);
q->next=p->next->next;
}
else
{
q->next=p->next;
p=q;
}
}
q=p;
p=p->next;
}
if (p!=h)
{
p->count--;
if (p->count<=0)
{
q->next=NULL;
}
}
if (h->count==0)
{
h=h->next;
}
ans++;
// p=h;
// while (p->next!=NULL)
// {
// cout<<p->data<<' '<<p->count<<'\n';
// p=p->next;
// }
// cout<<p->data<<' '<<p->count<<'\n';
}
cout<<ans;
}
看出来哪里错了吗?我一开始很自信自己是对的,直到我看到分数。当一个块被减光时,我是直接把它前一个块和后一个块连起来,却没有保证前后的块都没被减光,于是块就会比实际要多,然后就错了。我还是对着数据分析了半天才发现的。
不说了,还是欣赏一下我的AC代码吧。
#include<iostream>
#include<cstdio>
using namespace std;
struct node
{
char data;//data表示这一块的字母是什么
int count;//count表示这一块有几个连续的这样的数字
node *next;//指向下一个块
};
int n,ans;
string s;
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
cin>>n>>s;//其实n并没有什么用
node *h,*p,*q;
h=new node;
h->next=NULL;
h->data=s[0];
int i=1;
while (s[i]==s[0]&&i<s.size()) i++;//先把链表头处理掉
h->count=i;
p=h;
while (i<s.size())
{
q=new node;
q->data=s[i];
q->next=NULL;
int x=i;
while (s[i]==q->data&&i<s.size()) i++;
q->count=i-x;
p->next=q;
p=q;
}//以上是建立链表的过程
while (h!=NULL&&h->next!=NULL)//当链表中什么都没有了或者只剩下一块的时候就做完了
{
h->count--;//最左边的那一块只需要-1
p=h->next;
q=h;
while (p->next!=NULL)//遍历整个链表
{
p->count-=2;//中间的块要-2
if (p->count<=0) //如果这一块减光了,就不操作(这样就自动被删除了)
{
p=p->next;
continue;
}
if (q->data!=p->data)//如果这一块没被减光,就把上一个没被减光的next指向这一块
{
q->next=p;//跳过中间一些被减光的块
q=p;
}
else q->count+=p->count;//如果这两块的data一样,就可以合并
p=p->next;
}//头部和中间都处理完了,接下来要处理尾部
if (q->data==p->data)//尾部的合并
{
q->count+=p->count-1;//做完这一步,相当于尾部(p)没了,而q变成了新的尾部,-1不能漏掉
q->next=NULL;
}
else q->next=p;//跳过中间一些被减光的块
if (p!=h)//如果尾和头不是同一个块就-1
{
p->count--;
if (p->count<=0)//尾部没了
{
q->next=NULL;
}
}
if (h->count<=0)//头部没了的操作放后面会方便一点,起码能保证后面要么都没了,要么就是没被减光的块
{
h=h->next;
}
ans++;
//以下是遍历整个链表的操作,可以把每一块输出来
// p=h;
// while (p!=NULL&&p->next!=NULL)
// {
// cout<<p->data<<' '<<p->count<<" ";
// p=p->next;
// }
// if (p!=NULL) cout<<p->data<<' '<<p->count<<'\n';
}
cout<<ans;
}
总结
像这种细节很多的题,思路一定要清晰,考虑多种情况,自己要多测几组数据,并把中间过程多输出一点,这样才能发现一些错误。