题解:P14508 猜数游戏 guess
目前暂无修正。
发现难以直接在原序列上做,考虑划分子结构,只需解决长度为 \(i\) 的找隐藏点问题。有两种可能,第一种是找 \([1,n]\) 中隐藏点,当前在 \(1\),第二种是当前在 \(n+1\)。由于行走规定开出的区间是 \([l,r)\),所以两种情况是对称的。
考虑解决 \([1,i]\) 中找点问题,令 \(f_i\) 表示其最小代价,从 \(1\) 出发,枚举走的长度 \(j\),最小费用 \(cost_j\),即可划分为:
\[f_i=\min_{j=1}^{i-1}\{\max(f_j,f_{i-j})+cost_j\}
\]
答案即为 \(f_n\)。枚举状态 + 转移 \(O(n^2)\) 难以通过(吗?反正我没敢试),考虑缩小 \(j\) 上限,发现走多步可以拆分为若干个走一步,所以 \(j\) 只需要最大取到 \(V=\max a_i\)。
如何计算 \(cost_j\)?一个错误的思路是,完全背包处理加的部分再处理减的部分。暴力上完整容积的背包,复杂度显然是不对的。完全背包的下标转移实质上就相当于最短路转移,而如果压缩这个完全背包,难以找到一个有效的顺序正确计算其值(其实真正有效顺序类似于堆优化,一定先找最小的更新别人)。
发现求取 \(cost_j\) 本质上是从 \(0\) 出发,向右跳到点 \(j\) 需要的最小费用。采用堆优化最短路,由于只需要走一段前缀,需要建出点 \([-n,n]\) 以便进行先减后加的操作,又根据 \(j\) 的上界只需建出 \([-V,V]\),每次 \(O(m)\) 访问所有可能边即可,总边数是 \(O(mV)\) 的。
总时间复杂度 \(O(T(nV+mV\log {mV}))\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PII;
const int N=1e4+5,M=1e3+5;
const LL INF=1e16;
int n,m,mx;
LL cost[N*2],f[N];
struct Q{int a,b;}op[M];
priority_queue<PII,vector<PII>,greater<PII> >q;
inline void utov(int &u,int &v,int &w){
if(cost[u+N]+w<cost[v+N])
cost[v+N]=cost[u+N]+w,
q.push(make_pair(cost[v+N],v));
}
void Dij(){
for(int i=-mx;i<=mx;i++)
cost[i+N]=INF;
cost[N]=0;
q.push(make_pair(0,0));
while(!q.empty()){
PII tp=q.top();q.pop();
int u=tp.second;
if(tp.first!=cost[u+N])continue;
for(int i=1;i<=m;i++){
if(u+op[i].a<=mx){
int v=u+op[i].a;
utov(u,v,op[i].b);
}
if(u-op[i].a>=-mx){
int v=u-op[i].a;
utov(u,v,op[i].b);
}
}
}
}
int main(){
//freopen("guess.in","r",stdin);
//freopen("guess.out","w",stdout);
int Cn,Tn;scanf("%d%d",&Cn,&Tn);
while(Tn--){
mx=0;
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
f[i]=INF;
f[1]=0;
for(int i=1;i<=m;i++)
scanf("%d",&op[i].a),
mx=max(mx,op[i].a);
for(int i=1;i<=m;i++)
scanf("%d",&op[i].b);
Dij();
for(int i=2;i<=n;i++){
for(int j=1;j<=mx&&j<=i-1;j++)
f[i]=min(f[i],max(f[j],f[i-j])+cost[j+N]);
}
printf("%lld\n",f[n]<INF?f[n]:-1);
}
return 0;
}

浙公网安备 33010602011771号