转载请注明出处http://blog.csdn.net/beluma123,谢谢!
这是CSDN英雄会中的一道题,详见:原题链接.题目如下:有一行彩色的棋子,每个棋子的颜色是k种颜色之一.你不能改变棋子的顺序,但是可以移走一些棋子。问至少移走多少个石子,才能使得两个同色得石子之间没有其他颜色的棋子?(额...原题怎么一会石子一会又棋子的,明确起见,本文后面全部改用棋子)
输入格式:
多组数据,每组数据两行,第一行是两个整数n和k, 1<=n<=100, 1<=k<=5
下一行是n个在[1..k]范围内的正整数,代表每个棋子的颜色。
输出格式:
每组测试数据输出一行包含一个整数,表示至少移走的石子数。
输入样例:
10 3
2 1 2 2 1 1 3 1 3 3
输出样例:
2
注:可以移走第2个第7个棋子。
一.最基本的方法.
就是对所有的取法都判断是否满足"两个同色的棋子之间没有其他颜色的棋子"这一条件,最后再从满足此条件的所有结果中找出取走棋子个数最少的作为结果返回.思路没有错,但是运行结果也很明显:严重超时.
二.应用一个先验知识的改进.
通过思考可以得到一条重要的先验知识:
连成一串的相同颜色的棋子的取法是相同的
也就是说:在最优的取法中要么把连成一串的相同颜色的棋子全部取走,要么全部留下.例如输入样例中第3,4位置的"2",
可以用反证法证明:若最优取法中把某一串相同颜色的棋子取走了一部分留下了一部分,那么把从这一串中取走的棋子留下能有更优的结果.有了这条先验知识我们就可以把"一串相同颜色的棋子"当做一个整体来处理,我们之后称之为"串",这样能够提高我们处理的速度.尽管这样,我们的结果仍然是:超时.
三.在上述基础上进行宽度优先搜索
注意到:只要搜索到取走棋子最少的取法即可,没有必要把所有结果搜索一遍.也就是我们可以先判断所有的取走1个棋子的取法是否满足条件,若有满足条件的则结果为1,否则判断所有的取走2个棋子的取法是否满足条件,若有满足条件的则结果为2,......考虑到"串"的大小不一,实际的运行过程还要做改进,改进后的代码如下:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
typedef unsigned int uint;
struct NumAndCnt//就是颜色+连续出现的个数
{
uint Num;
uint Cnt;
bool Used;
uint IndexInOrg;//在原始vector中的索引
};
struct Path
{
vector<NumAndCnt> vtr_unused;//移除的
queue<NumAndCnt> qu_PotentialUnused;//以后可能移除的
uint dist;//已经移除的个数,就是vtr_unused中的所有Cnt之和
};
bool operator<(Path f,Path s)
{
if(f.dist >s.dist){return true;}
else{return false;}
}
uint Compute(vector<uint>&);
bool IsOk(vector<NumAndCnt>& vtr_in);
//把几个int装换成NumAndCnt格式
void Ints2NumAndCnts(vector<uint>& in,vector<NumAndCnt>& ret);
void main()
{
uint n,k;
vector<uint> vtr_ints;
while (cin>>n>>k)
{
uint temp;
vtr_ints.clear ();
for (uint i=1;i<=n;i++)
{
cin>>temp;
vtr_ints.push_back (temp);
}
cout<<Compute(vtr_ints)<<endl;
}
}
uint Compute(vector<uint>& vtr_ints)
{
if(vtr_ints.size ()==1){return 0;}
vector<NumAndCnt> vtr_NumCnt;
Ints2NumAndCnts(vtr_ints,vtr_NumCnt);
priority_queue<Path> Paths;//所有当前要处理的方案
Path path_init;
path_init.dist =0;
for(uint i=0;i<vtr_NumCnt.size ();i++)
{path_init.qu_PotentialUnused .push (vtr_NumCnt[i]);}
Paths.push (path_init);
while(!Paths.empty())
{
Path pathnow=Paths.top ();
Paths.pop ();
if(!pathnow.vtr_unused .empty ())
{
for(uint i=0;i<vtr_NumCnt.size ();i++)
{
vtr_NumCnt[i].Used =true;
}
for(uint i=0;i<pathnow.vtr_unused.size ();i++)
{
vtr_NumCnt[pathnow.vtr_unused[i].IndexInOrg].Used =false;
}
if(IsOk(vtr_NumCnt)){return pathnow.dist;}
}
if(pathnow.qu_PotentialUnused.size ()>=1)
{
Path newpath;
newpath.qu_PotentialUnused =pathnow.qu_PotentialUnused ;
uint temp=pathnow.qu_PotentialUnused.size ();
for(uint i=0;i<temp;i++)
{
newpath.vtr_unused=pathnow.vtr_unused ;
newpath.dist=pathnow.dist+newpath.qu_PotentialUnused.front().Cnt;
newpath.vtr_unused .push_back (newpath.qu_PotentialUnused.front());
newpath.qu_PotentialUnused .pop ();
Paths.push (newpath);
}
}
}
}
void Ints2NumAndCnts(vector<uint>& in,vector<NumAndCnt>& ret)
{
ret.clear ();
NumAndCnt now;
now.Num=in[0];
now.Cnt =0;
now.Used =true;
now.IndexInOrg =0;
for(uint i=0;i<in.size ();i++)
{
if(now.Num ==in[i])
{
now.Cnt ++;
}
else
{
ret.push_back (now);
now.Num =in[i];
now.Cnt =1;
now.IndexInOrg++;
}
}
ret.push_back (now);
}
bool IsOk(vector<NumAndCnt>& vtr_in)
{
//除去Unused
vector<NumAndCnt> vtr_used;
for(uint i=0;i<vtr_in.size ();i++)
{
if(vtr_in[i].Used){vtr_used.push_back (vtr_in[i]);}
}
if(vtr_used.empty ())return true;
//合并相邻的相同颜色的
vector<NumAndCnt> vtr_merged;
vtr_merged.push_back (vtr_used[0]);
if(vtr_used.size ()>1)
{
for(uint i=1;i<vtr_used.size ();i++)
{
if(vtr_merged.back ().Num==vtr_used[i].Num)
{
vtr_merged.back ().Cnt +=vtr_used[i].Cnt;
}
else
{
vtr_merged.push_back (vtr_used[i]);
}
}
}
else{return true;}
//判断合并后的有没有颜色相同的
for(uint i=0;i<vtr_merged.size ()-1;i++)
{
for(uint j=i+1;j<vtr_merged.size ();j++)
{
if(vtr_merged[i].Num ==vtr_merged[j].Num){return false;}
}
}
return true;
}
尽管这样,我们的运行结果还是:超时.
四.在上述基础上应用第二第三条先验知识
首先对待处理棋子分串,之后对各个颜色的串分别编号:对所有的颜色为"1"的串从左到右分别编号1,2,3......对所有颜色为"2"的串从从左到右编号:1,2,3......以此类推.第二条先验知识就是:
在最优的取法中,留下的相同颜色的串的编号必定是连续相邻的
例如:输入样例中的三个"1"串,不可能的取法是"留下最左边和最右边的"1"串而取走中间的"1"串"(不管其他颜色的串的取法),仍然可以用反证法证明,此处不再赘述.
我们把同一颜色的所有串中最左端的串称为这一颜色的"头",最右端的串称为这一颜色的"尾",那么第三条先验知识就是:
最优的取法最左端的颜色必定是原棋子最左端棋子这一颜色的"头"和"尾"之间的颜色以及最左端棋子颜色之一
这一论断仍然可用反证法证明,不再赘述.
综合运用上面的知识,得到流程:首先
对原棋子进行分串,之后对结果最左端颜色进行遍历(依照先验知识三),并且对每一种最左端颜色留下串的个数进行遍历(依照先验知识二),对余下的棋子运用此方法递归计算.最后比较取走的棋子个数,得到最优结果.
代码如下:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
typedef unsigned int uint;
#define MAXCLR 5//颜色最多5钟
#define INVALID_INDEX 1000//无用的索引,因为最多100个,所以可以定义为1000
struct ClrAndCnt//颜色以及连续出现的个数
{
uint Clr;
uint Cnt;
};
class InfoOfAClr//一种颜色的信息,
{
public:
uint Clr;
uint Idx_first;
uint Idx_last;
vector<uint> vtr_IdxAll;
};
//下面这个要进行递归
uint Compute(const vector<ClrAndCnt>& in);
//把几个int装换成ClrAndCnt格式
void Ints2NumAndCnts( const vector<uint>& in,vector<ClrAndCnt>& ret);
//扫描一个vector<ClrAndCnt>
void GetInfo(const vector<ClrAndCnt>& in,vector<InfoOfAClr>& ret);
void main()
{
uint n,k;
vector<uint> vtr_ints;
vector<ClrAndCnt> vtr_ClrAndCnt;
while (cin>>n>>k)
{
vtr_ints.clear ();
vtr_ClrAndCnt.clear ();
uint temp;
for (uint i=1;i<=n;i++)
{
cin>>temp;
vtr_ints.push_back (temp);
}
Ints2NumAndCnts(vtr_ints,vtr_ClrAndCnt);
cout<<Compute(vtr_ClrAndCnt)<<endl;
}
}
//返回一个vtr以某种颜色为头构成满足条件的结果移除的石子的个数最小值(包括移除此颜色前面的石子的个数)
uint SlctClrForHead(const vector<ClrAndCnt>& vtr_clrAndCnt,const InfoOfAClr& info)
{
vector<ClrAndCnt> vtr_CACForMe=vtr_clrAndCnt;
uint BasicDelCnt=info.Idx_first;//移除的在它之前的整个块的个数,为了erase能够使用
uint BasicDelStonesCnt=0;//基础移除的石子的个数
//移除在它之前的所有石子
if(info.Idx_first !=0)
{
for(uint i=1;i<=info.Idx_first;i++)
{
BasicDelStonesCnt+=vtr_CACForMe.front ().Cnt ;
vtr_CACForMe.erase (vtr_CACForMe.begin ());
}
}//之后也相当于vtr_CACForMe初始化了
uint delMin=1000;
for(int idxlastForMe=0;idxlastForMe<info.vtr_IdxAll .size ();idxlastForMe++)
{
vector<ClrAndCnt> vtr_CACForThis=vtr_CACForMe;
uint thisDelStonesCnt=0;
uint thisDelCnt=0;
for(uint idx=0;idx<=info.vtr_IdxAll[idxlastForMe]-BasicDelCnt;idx++)
{
if(vtr_CACForThis[idx].Clr !=info.Clr){thisDelStonesCnt+=vtr_CACForThis[idx].Cnt ;}
}
if(idxlastForMe!=info.vtr_IdxAll .size ()-1)
{
for(uint idx=info.vtr_IdxAll[idxlastForMe]-BasicDelCnt+1;idx<=info.Idx_last-BasicDelCnt;idx++)
{
if(vtr_CACForThis[idx].Clr ==info.Clr){thisDelStonesCnt+=vtr_CACForThis[idx].Cnt ;}
}
}
while(info.vtr_IdxAll[idxlastForMe]-BasicDelCnt-thisDelCnt!=0)
{
thisDelCnt++;
vtr_CACForThis.erase (vtr_CACForThis.begin ());
}
vector<ClrAndCnt>::iterator itr;
itr=vtr_CACForThis.begin ();
while(itr!=vtr_CACForThis.end ())
{
if((*itr).Clr ==info.Clr){vtr_CACForThis.erase (itr);itr=vtr_CACForThis.begin ();}
else{itr++;}
}
uint DelForthis=thisDelStonesCnt+Compute(vtr_CACForThis);
if(delMin>DelForthis){delMin=DelForthis;}
}
return delMin+BasicDelStonesCnt;
}
uint Compute(const vector<ClrAndCnt>& vtr_clrAndCnt)
{
if(vtr_clrAndCnt.size ()<=1){return 0;}
vector<InfoOfAClr> vtr_InfoScan;
GetInfo(vtr_clrAndCnt,vtr_InfoScan);
uint firstClr=vtr_clrAndCnt[0].Clr ;
uint fstClrLast=vtr_InfoScan[firstClr].Idx_last;
vector<InfoOfAClr> ClrsHead;
//寻找所有可以作为头的颜色
for(uint i=1;i<vtr_InfoScan.size ();i++)
{
if(vtr_InfoScan[i].Idx_first<=fstClrLast){ClrsHead.push_back (vtr_InfoScan[i]);}
}
uint min=10000;
for(uint i=0;i<ClrsHead.size ();i++)
{
uint nowdel=SlctClrForHead(vtr_clrAndCnt,ClrsHead[i]);
if(min>nowdel){min=nowdel;}
}
return min;
}
void Ints2NumAndCnts(const vector<uint>& in,vector<ClrAndCnt>& ret)
{
ClrAndCnt now;
now.Clr=in[0];
now.Cnt =0;
for(uint i=0;i<in.size ();i++)
{
if(now.Clr ==in[i])
{
now.Cnt ++;
}
else
{
ret.push_back (now);
now.Clr =in[i];
now.Cnt =1;
}
}
ret.push_back (now);
}
void GetInfo(const vector<ClrAndCnt>& in,vector<InfoOfAClr>& ret)
{
ret.clear ();
InfoOfAClr info_temp;
for(uint i=0;i<=MAXCLR;i++)
{
info_temp.Clr =i;
info_temp.Idx_first=INVALID_INDEX;
info_temp.Idx_last =INVALID_INDEX;
ret.push_back (info_temp);
}
//开始扫描
for(uint i=0;i<in.size ();i++)
{
uint now_Clr=in[i].Clr;
if(ret[now_Clr].Idx_first==INVALID_INDEX ){ret[now_Clr].Idx_first=i;}
ret[now_Clr].vtr_IdxAll.push_back (i);
}
for(uint i=1;i<ret.size ();i++)
{
if(!ret[i].vtr_IdxAll .empty ()){ret[i].Idx_last =ret[i].vtr_IdxAll.back ();}
}
}
代码测试结果:通过!
中间有许多代码写的不够简洁,讲解不够清楚,并且应该还有高效的算法,还望大神指正.