2013D2T1
积木赛
Task
n个初始为0的数,求最少的操作次数变成目标数组。
一次操作:选择区间[ l , r ]内所有数+1
n<=1e5,0<=hi<=1e4
Solution
贪心:
为使操作次数最少,每次选的区间应尽可能的大。因此每次贪心选一段最长都是正数的区间,区间内数-1.最值可以用堆维护。
如果当前选出的区间操作完后不含0,那么下一次还是会选择这个区间,为减少操作次数,
如果一次性减去这个区间的最小值,就一定会含0,区间分成2个子区间。
用堆写还是太麻烦了,不如dfs,每次找到这个区间内的最小值,分成2个子区间。
const int M=1e5+5;
int A[M],n;
struct node{
int l,r,x;
bool operator<(const node &t)const{
r-l>t.r-t.l;
}
};
priority_queue<node>Q;
inline void input(){
int i;
rd(n);
rep(i,1,n)rd(A[i]);
}
inline void add(int L,int R,int x){
int i,k,mi,l,r;
rep(i,L,R)A[i]-=x;
rep(i,L,R){
if(A[i-1]==0&&A[i]>0){
l=i;
mi=A[i];
}
MIN(mi,A[i]);
if(A[i]>0&&A[i+1]==0){
Q.push((node){l,i,mi});
}
}
}
inline void solve(){
int i,j,k,ans=0,l,r;
node res;
while(!Q.empty()){
res=Q.top();
Q.pop();
ans+=res.x;
add(res.l,res.r,res.x);
}
printf("%d\n",ans);
}
int main(){
input();
add(1,n,0);
solve();
return 0;
}
LIN452大神提供了一种O(n)的方法。
刷漆:
区间加除了线段树以外,还可以用刷漆法处理,左端点+1,右端点+1的位置-1。
对于i点而言,权值从0变成hi,说明[1,i]操作前缀和是hi。对于i-1点而言,操作的前缀和是h[i-1],说明对i点的操作数是h[i]-h[i-1],是以i为操作区间左端点的最少次数。
const int M=1e5+5;
int A[M];
int n;
int main(){
int i,j,k,ans=0;
rd(n);
rep(i,1,n)rd(A[i]);
rep(i,1,n)ans+=max(0,A[i]-A[i-1]);
printf("%d\n",ans);
}
2013D2T2
花匠
Task
在n个数去除一些数,使剩余的数满足条件A或条件B,求最多剩余数?
条件A:对所有的i,g[2i]>g[2i-1],g[2i]>g[2i+1]
条件B:对所有的i,g[2i]小于g[2i-1],g[2i]小于g[2i+1]
n<=1e5,hi<=1e6
Solution
考察LIS的dp思想。
发现最后满足的数是波浪锯齿状,且2个为1组。每个数的选择:不选,组内第一个数,组内第二个数。每一个数都是接在符合条件的另一个数后面。
如果定义dp[0][i],i作为组第一个,dp[1][i]i作为组第二个。
条件A:
Dp[0][i]=max(dp[1][j])+1 j小于i&&A[j]>A[i]
Dp[1][i]=max(dp[0][i])+1 j小于i&&A[j]小于A[i]
同样的条件B的式子也可以列出。
普通的转移是O(n^2)
但是我们可以借助线段树优化,因为是维护前缀后缀的最值,也可以用树状数组来代替,代码更短,效率更高。
但是比赛时以为后缀的下标搞错了,一个测试点WA了。
X对应的后缀下标是tot-x+1.因为[1,tot] 对应[tot,1]
const int M=1e5+5;
int A[M],B[M];
int n,ans,tot;
struct P70{
int dp[2][1005];
inline void solve(){
int i,j,k,ans=0;
rep(i,1,n)rd(A[i]);
A[0]=1e9;
rep(i,1,n){//条件A
rep(j,0,i-1){
if(A[j]>A[i])MAX(dp[0][i],dp[1][j]+1);
if(A[j]<A[i])MAX(dp[1][i],dp[0][j]);
}
}
rep(i,1,n){
MAX(ans,dp[0][i]*2-1);
MAX(ans,dp[1][i]*2);
}
memset(dp,0,sizeof(dp));
A[0]=-1;
rep(i,1,n){
rep(j,0,i-1){
if(A[j]<A[i])MAX(dp[0][i],dp[1][j]+1);
if(A[j]>A[i])MAX(dp[1][i],dp[0][j]);
}
}
rep(i,1,n){
MAX(ans,dp[0][i]*2-1);
MAX(ans,dp[1][i]*2);
}
printf("%d\n",ans);
}
}P70;
struct Tree{
int bit[M];
inline void clear(){memset(bit,0,sizeof(bit));}
inline void modify(int i,int x){
while(i<=tot){
MAX(bit[i],x);
i+=lowbit(i);
}
}
inline int query(int i){
int mx=0;
while(i>0){
MAX(mx,bit[i]);
i-=lowbit(i);
}
return mx;
}
}T[2];
struct P100{
inline void init(){
int i,j,k;
rep(i,1,n)rd(A[i]),B[i-1]=A[i];
sort(B,B+n);
tot=unique(B,B+n)-B;
rep(i,1,n)//离散
A[i]=lower_bound(B,B+tot,A[i])-B+1;
}
inline void solve(){
int i,j,k,ans=0,a,b;
init();
T[0].clear();T[1].clear();
rep(i,1,n){
a=T[1].query(tot-A[i])+1;
b=T[0].query(A[i]-1)+1;
T[0].modify(A[i],a);
T[1].modify(tot-A[i]+1,b);
MAX(ans,max(a,b));
}
T[0].clear();T[1].clear();
rep(i,1,n){
a=T[1].query(A[i]-1)+1;
b=T[0].query(tot-A[i])+1;
T[0].modify(tot-A[i]+1,a);
T[1].modify(A[i],b);
MAX(ans,max(a,b));
}
printf("%d\n",ans);
}
}P100;
int main(){
rd(n);
if(n<=1000){P70.solve();return 0;}
P100.solve();
return 0;
}
2013D2T3
华容道
Task
N*m棋盘上有n*m-1个棋子,1个空白格,棋子有若干个不能移动,剩余的可以移动,空白格只能和四周可以移动的格子交换位置。
Q个询问,除了不能移动的棋子外,空白格,其余棋子,初始目标位置都可能改变,求某棋子从初始位置到目标位置的最小步数。
60%数据,n,m<=30,q<=10
100% 数据,n,m<=30,q<=500
Solution
60分bfs
类似judge1205推箱子,从初始位置移动到目标位置的最小步数,唯一不同的是该题是多次询问。bfs可以求解最小步数的问题。
因为我们只关心空白格子和初始特别格子的位置,因此在bfs时只用记录这2个的坐标。
搜索时,注意判重,不合法的情况。
值得一提的是,Y同学用dfs?bfs?做没有判重,电脑死机了。唉╮(╯▽╰)╭
100分 最短路
题目中有一句话:要将指定块移入目标位置,必须先将空白块移入目标位置。给我带来了很大的提示。
更广泛的说,如果x要达到某个位置,必须先将空白格移到该位置,再交换,且该位置一定在x的四周。
60分是搜索空白格的路径,但100分是搜索x的路径,相比60分而言,同样是bfs,但是主体不同。可以一步代替多步,从无方向到有方向。
如果我已知x位置和空白格位置(一定在x的四周)那么x只能和空白格交换,因此可以得知x的新位置,空白格也必须移动到x的四周。
因此可以预处理点(x,y)在不经过中心点的情况下,到达中心点四周的最短路。
const int M=30;
int rx[]={0,1,0,-1},ry[]={1,0,-1,0};
int dis[M][M][4][M][M],val[M][M][4];
bool A[M][M],vis[M][M],use[M][M][4];
int n,m,q;
struct qu{
int x,y;
}Qu[905];
struct node{
int x,y,d,v;
bool operator<(const node &t)const{
return v>t.v;
}
};
priority_queue<node>Q;
inline void input(){
int i,j,k;
rd(n);rd(m);rd(q);
rep(i,0,n-1)
rep(j,0,m-1)rd(A[i][j]);
}
inline bool check(int x,int y){
return x>=0&&x<n&&y>=0&&y<m&&A[x][y]==1;
}
inline void bfs(int a,int b,int p){
int i,j,k,x,y,l,r,nx,ny;
qu res;
x=a+rx[p],y=b+ry[p];
memset(vis,0,sizeof(vis));
vis[a][b]=vis[x][y]=1;
l=r=dis[a][b][p][x][y]=0;
Qu[r++]=(qu){x,y};
while(l<r){
res=Qu[l++];
rep(i,0,3){
nx=res.x+rx[i],ny=res.y+ry[i];
if(!check(nx,ny)||vis[nx][ny])continue;
vis[nx][ny]=1;
dis[a][b][p][nx][ny]=dis[a][b][p][res.x][res.y]+1;
Qu[r++]=(qu){nx,ny};
}
}
}
inline void init(){
int i,j,k,ii,jj;
memset(dis,-1,sizeof(dis));
rep(i,0,n-1)
rep(j,0,m-1){
if(A[i][j]==0)continue;
rep(k,0,3)
if(check(i+rx[k],j+ry[k])){
bfs(i,j,k);
}
}
}
inline int dijkstra(int tx,int ty){
int i,j,k,x,y;
node res;
while(!Q.empty()){
res=Q.top();Q.pop();
if(use[res.x][res.y][res.d])continue;
if(res.x==tx&&res.y==ty)return val[res.x][res.y][res.d];
use[res.x][res.y][res.d]=1;
x=res.x+rx[res.d],y=res.y+ry[res.d];
rep(i,0,3){
k=dis[x][y][i][res.x][res.y];
if(k>=0){
if(res.v+k+1<val[x][y][i])val[x][y][i]=res.v+k+1;
Q.push((node){x,y,i,res.v+k+1});
}
}
}
return -1;
}
inline void solve(){
int i,j,k,wx,wy,bx,by,tx,ty;
while(q--){
rd(wx);rd(wy);rd(bx);rd(by);rd(tx);rd(ty);
wx--,wy--,bx--,by--,tx--,ty--;
if(bx==tx&&by==ty){puts("0");continue;}
memset(use,0,sizeof(use));
memset(val,0x3f,sizeof(val));
while(!Q.empty())Q.pop();
rep(i,0,3){
k=dis[bx][by][i][wx][wy];
if(~k){
val[bx][by][i]=k;
Q.push((node){bx,by,i,k});
}
}
printf("%d\n",dijkstra(tx,ty));
}
}
int main(){
input();
init();
solve();
return 0;
}