题目大意:
有一排n个格子和2枚硬币。
现在有q次任务,每一次要你把其中一枚硬币移到x的位置上,移动1格的代价是1。
两枚硬币不能同时移动,任务必须按次序完成。
现在告诉你两枚硬币初始状态所在的位置a和b,问完成所有任务的最小代价。
思路:
很容易想到一个O(qn)的DP。
由于完成任务的次序确定,每个任务的位置也确定,我们可以用f[i][j]表示完成第i个任务后,一个硬币在x[i],一个硬币在j的最小代价。
转移方程为f[i][j]=min{f[i-1][j]+|x[i]-x[i-1]|},f[i][a[i-1]]=min{f[i-1][j]+|x[i]-j|}。
然而这样还是会TLE,在AtCoder上只过了14/34的测试数据。
不难发现,在状态转移方程中,如果我们能去掉绝对值,里面的东西就能用线段树维护。
而绝对值的取值只与硬币的左右位置关系有关。
因此我们可以建2棵线段树,一棵表示被转移的状态在目标状态左边,一棵表示在右边。
左线段树中每个叶子结点x[i-1]维护f[i-1][j]-x[i-1]的值,右线段树每个叶子结点x[i-1]维护f[i-1][j]+x[i-1]的值。
看了一下榜,发现排在前面的基本上都是用树状数组做的。
然而用树状数组维护区间最值难道不是O(log^2 n)的吗?
事实上我们可以发现线段树上维护的东西只会越来越小,这样我们可以直接在树状数组上修改,不用考虑原来的最小值没了怎么办。
然后我又在树状数组里面加了一个剪枝。
这样随随便便就能拿Rank1。
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<algorithm>
typedef signed long long int int64;
inline unsigned getint() {
register char ch;
while(!isdigit(ch=getchar()));
register unsigned x=ch^'';
while(isdigit(ch=getchar())) x=(((x<<)+x)<<)+(ch^'');
return x;
}
inline int64 min(const int64 &a,const int64 &b) {
return a<b?a:b;
}
const int64 inf=0x7ffffffffffffff;
const int N=;
int n;
class FenwickTree {
private:
int64 val[N];
int lowbit(const int &x) const {
return x&-x;
}
public:
FenwickTree() {
std::fill(&val[],&val[N],inf);
}
void modify(int p,const int64 &x) {
while(p<=n) {
if(x<val[p]) {
val[p]=x;
} else {
return;
}
p+=lowbit(p);
}
}
int64 query(int p) const {
int64 ret=inf;
while(p) {
ret=min(ret,val[p]);
p-=lowbit(p);
}
return ret;
}
};
FenwickTree ta;
class RevFenwickTree {
private:
int64 val[N];
int lowbit(const int &x) const {
return x&-x;
}
public:
RevFenwickTree() {
std::fill(&val[],&val[N],inf);
}
void modify(int p,const int64 &x) {
while(p) {
if(x<val[p]) {
val[p]=x;
} else {
return;
}
p-=lowbit(p);
}
}
int64 query(int p) const {
int64 ret=inf;
while(p<=n) {
ret=min(ret,val[p]);
p+=lowbit(p);
}
return ret;
}
};
RevFenwickTree tb;
int64 f[N];
inline void modify(const int &p,const int64 x) {
if(x<f[p]) {
f[p]=x;
ta.modify(p,x-p);
tb.modify(p,x+p);
}
}
int main() {
n=getint();
int q=getint(),a=getint(),b=getint();
std::fill(&f[],&f[N],inf);
modify(a,);
int64 sum=;
while(q--) {
a=b;
b=getint();
sum+=abs(a-b);
int64 t1=ta.query(b)+b,t2=tb.query(b)-b;
modify(a,min(t1,t2)-abs(a-b));
}
int64 tmp=inf;
for(register int i=;i<=n;i++) {
tmp=min(tmp,f[i]);
}
printf("%lld\n",tmp+sum);
return ;
}
原来的O(n^2)DP程序:
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
inline unsigned getint() {
register char ch;
while(!isdigit(ch=getchar()));
register unsigned x=ch^'';
while(isdigit(ch=getchar())) x=(((x<<)+x)<<)+(ch^'');
return x;
}
inline unsigned min(const unsigned &a,const unsigned &b) {
return a<b?a:b;
}
const unsigned N=;
unsigned long long f[][N];
unsigned a[];
int main() {
unsigned n=getint(),q=getint();
memset(f[],0xff,n<<);
a[]=getint()-,f[][getint()-]=;
for(register unsigned i=;i<=q;i++) {
a[i&]=getint()-;
memset(f[i&],0xff,n<<);
for(register unsigned j=;j<n;j++) {
if(!~f[~i&][j]) continue;
f[i&][j]=min(f[i&][j],f[~i&][j]+abs(a[i&]-a[~i&]));
f[i&][a[~i&]]=min(f[i&][a[~i&]],f[~i&][j]+abs(a[i&]-j));
}
}
unsigned long long ans=~;
for(register unsigned i=;i<n;i++) {
ans=min(ans,f[q&][i]);
}
printf("%llu\n",ans);
return ;
}