P14384 [JOISC 2017] 烟花棒 / Sparklers题解
前置芝士:如下。
P9870 双序列拓展
给定初始序列 \(X,Y\),有 \(q\) 次询问,每次询问会修改 \(X,Y\) 中的一些值。你可以复制其中一些元素从而组成一个长度为 \(10^{100}\) 的序列 \(f,g\)。若存在一种方式使得 \(f_i<g_i\) 或 \(f_i>g_i\),则输出 \(1\),否则输出 \(0\)。
解法
考虑转换问题。原题中的部分分启发我们写出一个 DP,然后不难发现这个 DP 可以理解为在一个网格图上,每次向右、右下、下的 \([X_i<Y_j]\) 的格子走一步,求解的是能否走到 \((n,m)\)。然后我们就成功把这个问题双射到了一个网格图上。
然后就是原题这个极其神秘的特殊性质:
对于每组询问(包括初始询问和额外询问),保证 \(x_1 < y_1\),且 \(x_n\) 是序列 \(X\) 唯一的一个最小值,\(y_m\) 是序列 \(Y\) 唯一的一个最大值。
都明摆着告诉你正解就要考虑这个了嘛。这个性质也较为简单,递归维护前缀最小/最大值即可。
至于正解,直接找到 \(X_{min},Y_{max}\),然后画一个图就可以发现,啥也没发现(不是)。
为什么呢,因为上一个性质我们没有考虑更进一步。直接考虑性质的递归在干什么。我们发现,这个性质有一个推导出来的性质:在我们的网格图上,第 \(n\) 行和第 \(m\) 列全都满足能走的条件。于是,我们不妨将它们标记。然后推向一般。发现,现在的图形成了一个十字路口状物。所以,只需要两个 check 判断左上和右下是否满足条件。代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+10;
int f[MAXN],g[MAXN],n,m,tx[MAXN],ty[MAXN],ttx[MAXN],tty[MAXN],c,q;
struct node
{
int mi,ma;
node(int mi_=0,int ma_=0):mi(mi_),ma(ma_){}
}pre[MAXN],pre2[MAXN],suf[MAXN],suf2[MAXN];
#define upd(a,p) (node((a[i]<a[p.mi]?i:p.mi),(a[i]>a[p.ma]?i:p.ma)))
bool check1(int x,int y,int n,int m)
{
if(x==1||y==1) return true;
node X=pre[x-1],Y=pre2[y-1];
if(f[X.mi]<g[Y.mi]) return check1(X.mi,y,n,m);
if(g[Y.ma]>f[X.ma]) return check1(x,Y.ma,n,m);
return false;
}
bool check2(int x,int y,int n,int m)
{
if(x==n||y==m) return true;
node X=suf[x+1],Y=suf2[y+1];
if(f[X.mi]<g[Y.mi]) return check2(X.mi,y,n,m);
if(g[Y.ma]>f[X.ma]) return check2(x,Y.ma,n,m);
return false;
}
bool solve(int tf[],int tg[],int n,int m)
{
if(tf[1]>=tg[1]) return false;
for(int i=1;i<=n;i++) f[i]=tf[i];
for(int i=1;i<=m;i++) g[i]=tg[i];
for(int i=1;i<=n;i++) pre[i]=(i==1)?node(1,1):upd(f,pre[i-1]);
for(int i=1;i<=m;i++) pre2[i]=(i==1)?node(1,1):upd(g,pre2[i-1]);
for(int i=n;i>=1;i--) suf[i]=(i==n)?node(n,n):upd(f,suf[i+1]);
for(int i=m;i>=1;i--) suf2[i]=(i==m)?node(m,m):upd(g,suf2[i+1]);
node X=pre[n],Y=pre2[m];
if(f[X.mi]>=g[Y.mi]||g[Y.ma]<=f[X.ma]) return false;
return check1(X.mi,Y.ma,n,m)&&check2(X.mi,Y.ma,n,m);
}
signed main()
{
cin>>c>>n>>m>>q;
for(int i=1;i<=n;i++) scanf("%d",&tx[i]);
for(int i=1;i<=m;i++) scanf("%d",&ty[i]);
cout<<(solve(tx,ty,n,m)||solve(ty,tx,m,n));
while(q--)
{
for(int i=1;i<=n;i++) ttx[i]=tx[i];
for(int i=1;i<=m;i++) tty[i]=ty[i];
int kx,ky;
scanf("%d%d",&kx,&ky);
while(kx--)
{
int x,y;
scanf("%d%d",&x,&y);
ttx[x]=y;
}
while(ky--)
{
int x,y;
scanf("%d%d",&x,&y);
tty[x]=y;
}
cout<<(solve(ttx,tty,n,m)||solve(tty,ttx,m,n));
}
}
PS:为什么没有图片涅?因为太丑
正片开始
P14384 [JOISC 2017] 烟花棒 / Sparklers
求一个最小的速度上限 \(s\),满足在每个仙女棒能燃烧 \(T\) 秒时,第 \(K\) 个人能够点燃全部 \(N\) 个人的仙女棒。其中,第 \(i\) 个人离第 \(1\) 个人 \(X_i\) 米。
解法
最大值最小,考虑二分答案。
显然有如下结论:
- 点燃了的人是一个区间
这个易证。因为如果不是一个区间,显然从一个区间到另一个区间会经过若干个人,而这些人也点燃显然更优。
- 未点燃的人始终朝已点燃的人前进。
非常显然。
- 点燃的人只在 \(T\) 秒后传递火。
你可以使那些没点燃的人跟着你跑,最后转移火即可。
- 只会有一个人被点燃。
由结论 3 可得,只需要一直跟着就可以了。
那这个时候就有人要问了,如果有多个人被点燃,为和不向后跑,把那些追赶的人点燃呢?这样不是更优吗?
但显然我们不需要考虑这个。如果能点燃证明速度是足够的,毕竟又不是问时间。
我们考虑一个区间 \([l,r]\) 能被传递的必要条件。由结论 2 可得,相对速度为 \(2s\),共能燃烧 \(T\) 秒,有 \(r-l\) 个人(左端点或右端点是初始引燃),距离为 \(X_r-X_l\)。则不难得出,条件为 \(x_r-x_l\le 2Ts(r-l)\)。
什么?标签有 Ad-hoc?那就要用 trick 了。考虑 tricky 一点的东西,设 \(b_i=2Tsi-X_i\),则条件变为 \(b_l\le b_r\)。这不就是双拓展序列吗!那么就可以写代码了。完整代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=100010;
int n,k,T,a[MAXN],b[MAXN],pre[MAXN],pre2[MAXN],suf[MAXN],suf2[MAXN];
bool dfs1(int x,int y)
{
if(x==k||y==k) return true;
if(b[suf[x+1]]<=b[pre[y]]) return dfs1(suf[x+1],y);
if(b[suf2[x]]<=b[pre2[y-1]]) return dfs1(x,pre2[y-1]);
return false;
}
bool dfs2(int x,int y)
{
if(x==1||y==n) return true;
if(b[pre[x-1]]<=b[suf[y]]) return dfs2(pre[x-1],y);
if(b[pre2[x]]<=b[suf2[y+1]]) return dfs2(x,suf2[y+1]);
return false;
}
bool check(int x)
{
for(int i=1;i<=n;i++) b[i]=2*T*x*i-a[i];
int l=k,r=k,ma=-1e18-7,mi=1e18+7;
for(int i=1;i<=k;i++)
{
if(b[i]<b[l]) l=i;
ma=max(ma,b[i]);
}
for(int i=k;i<=n;i++)
{
if(b[i]>b[r]) r=i;
mi=min(mi,b[i]);
}
if(b[l]>mi||b[r]<ma) return false;
suf[k]=suf2[k]=k;
for(int i=k-1;i>=l;i--)
{
if(b[i]<b[suf[i+1]]) suf[i]=i;
else suf[i]=suf[i+1];
if(b[i]>b[suf2[i+1]]) suf2[i]=i;
else suf2[i]=suf2[i+1];
}
pre[k]=pre2[k]=k;
for(int i=k+1;i<=r;i++)
{
if(b[i]<b[pre[i-1]]) pre[i]=i;
else pre[i]=pre[i-1];
if(b[i]>b[pre2[i-1]]) pre2[i]=i;
else pre2[i]=pre2[i-1];
}
if(!dfs1(l,r)) return false;
pre[1]=pre2[1]=1;
for(int i=2;i<=l;i++)
{
if(b[i]<b[pre[i-1]]) pre[i]=i;
else pre[i]=pre[i-1];
if(b[i]>b[pre2[i-1]]) pre2[i]=i;
else pre2[i]=pre2[i-1];
}
suf[n]=suf2[n]=n;
for(int i=n-1;i>=r;i--)
{
if(b[i]<b[suf[i+1]]) suf[i]=i;
else suf[i]=suf[i+1];
if(b[i]>b[suf2[i+1]]) suf2[i]=i;
else suf2[i]=suf2[i+1];
}
return dfs2(l,r);
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>k>>T;
for(int i=1;i<=n;i++) cin>>a[i];
int l=0,r=1e9,ans;
while(l<=r)
{
int mid=l+r>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans;
}
结束辣!

浙公网安备 33010602011771号