Yandex.Algorithm 2018, final round
链接
A. Smart Vending
如果售货机中的硬币个数无限,那么一定可以花到钱不够了位置,即 \(\lfloor\frac{b\times 10^6+c}{r}\rfloor\)
考虑任何时刻其实只有两种选择:全部用整钱,或者整钱+散钱。
如果 \(c+d\geq 10^6\),那么两种选择中至少有一种合法,那么无论如何都可以花完所有钱。
否则两种选择中至多有一种合法,也就是说只有唯一的购买方案。考虑总硬币数不变,所以每次手中剩余的硬币数可以看成 \(d\rightarrow d'\) 的过程,如果形成环了就除以环长即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
const ll m=1000000;
using namespace std;
int a[m],b,c,r,d;ll res=0;
bool get_next()
{
if(b<0 || c<0 || d<0) return false;
if(r%m>c) b-=r/m+1,c+=m-r%m,d-=m-r%m;
else b-=r/m,c-=r%m,d+=r%m;
if(b<0 || c<0 || d<0) return false;
res++;
return true;
}
int main()
{
scanf("%d%d%d%d",&b,&c,&r,&d);
res=(b*m+c)/r;
if(c+d>=m){printf("%lld\n",res);return 0;}
res=0;
while(1)
{
if(a[c])
{
ll w=1ll*m*(a[c]-b)/r;
res+=w*(b/(a[c]-b));
b%=(a[c]-b);
break;
}
a[c]=b;
if(!get_next()) break;
}
while(get_next());
printf("%lld\n",res);
return 0;
}
B. LIS vs. LDS
可以发现无论如何 LIS 和 LDS 最多有一个公共点。
为了方便,我们假设添加点 \((0,0),(n+1,0)\),这样 LIS 和 LDS 一定有交。考虑此时合法的情况,此时交点处是没有点的,也就是两种中的一种:

首先对于判断合法性,显然直接求出两种情况下序列长度之和的最大值,判断是否和 LIS 长度加 LDS 长度之和相同。
考虑如何求出序列长度之和最大值。首先考虑左边的情况,我们枚举蓝线的左端点,那么蓝线一定会找到一个尽可能小的右端点。
考虑此时的交点落在的区域 \([x,x+1],[y,y+1]\):

那么其左下,右上是可行的红色区域,反之同理。
以交点(黄色点)纵坐标建线段树,横坐标作扫描线。加入一条蓝线的贡献。
假如已经得到了原序列 LDS,注意由于这里是贪心取的,故每个位置都是尽可能大的值,故 \(\geq p\) 的 LDS 长度等同于 LDS 序列中 \(\geq p\) 的位置数。
考虑什么情况下会不合法:

加入一个点后可能有一部分点,不符合条件,有一部分点变多了,但是可以发现这对于任意一种颜色,只会导致答案 \(+1\) 或 \(-1\)。
复杂度 \(O(n\log n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500010
#define inf 100000000
#define MP make_pair
#define fi first
#define se second
#define LB lower_bound
using namespace std;
typedef pair<int,int> P;
int a[N],b[N];
namespace LDIS{
int f[N],g[N],fn,gn;
void init(int n){for(int i=1;i<=n+1;i++) f[i]=g[i]=inf;fn=gn=0;}
void answer(int v,P &r,P &s)
{
int w=inf-v;
r=MP(LB(f+1,f+fn+1,v)-f,LB(g+1,g+gn+1,w)-g);
s=MP(f[r.fi],g[r.se]);
if(r.fi>fn) fn++;if(r.se>gn) gn++;
f[r.fi]=v;g[r.se]=w;
}
}
namespace SGT{
P val[N<<2];int tag[N<<2];
void setg(int x,int v){val[x].fi+=v;tag[x]+=v;}
void push(int u){if(tag[u]) setg(u<<1,tag[u]),setg(u<<1|1,tag[u]);tag[u]=0;}
void build(int u,int l,int r,int a[])
{
if(l==r){val[u]=MP(a[l],l);return;}
int mid=(l+r)>>1;
build(u<<1,l,mid,a);build(u<<1|1,mid+1,r,a);
val[u]=max(val[u<<1],val[u<<1|1]);
}
void insert(int u,int l,int r,int L,int R,int v)
{
if(L>R) return;
if(L<=l && r<=R){setg(u,v);return;}
push(u);
int mid=(l+r)>>1;
if(L<=mid) insert(u<<1,l,mid,L,R,v);
if(R>mid) insert(u<<1|1,mid+1,r,L,R,v);
val[u]=max(val[u<<1],val[u<<1|1]);
}
P answer(int u,int l,int r,int L,int R)
{
if(L<=l && r<=R) return val[u];
push(u);
int mid=(l+r)>>1;
if(L>mid) return answer(u<<1|1,mid+1,r,L,R);
if(R<=mid) return answer(u<<1,l,mid,L,R);
return max(answer(u<<1,l,mid,L,R),answer(u<<1|1,mid+1,r,L,R));
}
}
P fl[N],fr[N],g[N],ans;
int hl[N],hr[N];
int a1[N],a2[N],n1,n2;
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
LDIS::init(n+1);
for(int i=n;i>=1;i--) LDIS::answer(a[i],fr[i],g[i]);
for(int i=1,l=1,r=n;i<=n+1;i++)
{
while(LDIS::f[l]<i) l++;
while(LDIS::g[r]>inf-i) --r;
b[i]=l+r-1;
}
LDIS::init(n+1);
SGT::build(1,1,n+1,b);
int res=0;
for(int i=1;i<=n;i++)
{
P s,t=g[i];LDIS::answer(a[i],fl[i],s);
s.se=inf-s.se;t.se=inf-t.se;
if(s.fi<t.fi) SGT::insert(1,1,n+1,s.fi+1,t.fi,-1);
else SGT::insert(1,1,n+1,t.fi+1,s.fi,1);
if(s.se<t.se) SGT::insert(1,1,n+1,s.se+1,t.se,1);
else SGT::insert(1,1,n+1,t.se+1,s.se,-1);
if(ans<SGT::val[1]) ans=SGT::val[1],res=i;
}
n1=LDIS::fn,n2=LDIS::gn;
if(ans.fi!=n1+n2){puts("IMPOSSIBLE");return 0;}
for(int i=1;i<=n;i++) hl[i]=inf,hr[i]=0;
for(int i=1;i<=res;i++) hl[fl[i].fi]=hr[fl[i].se]=a[i];
int l=0,r=0;
for(;hl[l+1]<ans.se;l++);
for(;hr[r+1]>=ans.se;r++);
for(int i=res,p=l;i;i--) if(fl[i].fi==p) a1[p--]=i;
for(int i=res,p=r;i;i--) if(fl[i].se==p) a2[p--]=i;
reverse(a1+1,a1+n1+1);reverse(a2+1,a2+n2+1);
for(int i=res+1,p=n1-l;i<=n;i++) if(fr[i].se==p) a1[p--]=i;
for(int i=res+1,p=n2-r;i<=n;i++) if(fr[i].fi==p) a2[p--]=i;
reverse(a1+1,a1+n1+1);reverse(a2+1,a2+n2+1);
printf("%d\n",n1);
for(int i=1;i<=n1;i++) printf("%d ",a1[i]);
printf("\n%d\n",n2);
for(int i=1;i<=n2;i++) printf("%d ",a2[i]);
return 0;
}
D. Search Engine
如果建出了整棵树的后缀树,那么一次后加操作等价于往某个儿子处跳了一步(注意此时的点可能不存在),前加操作等同于跳后缀自动机的转移边。
如果全部建出复杂度显然爆炸。而有一个结论是,如果选择了后加操作,那么一定会加到下一个已经建出的节点。
考虑证明,因为前加之后的 endpos 集合必然是加之前的子集,故如果当前点 \(u\) 没有被建出来,那么转以后的 \(u'\) 也一定不会被建出来。
如果 \(u\) 转移到 \(u'\) 更优,那么显然从 \(u\) 的父亲开始就转移过去一定更优,因为被缩掉的一条链上 endpos 集合是全等的。
所以直接做一遍 dp,复杂度 \(O(n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 400010
#define C 26
#define ll long long
using namespace std;
int ch[N][C],fa[N],len[N],siz[N],las=1,cnt=1;
void insert(int c)
{
int p=las,q=las=++cnt;
len[q]=len[p]+1;siz[q]++;
for(;p && !ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else
{
int np=ch[p][c];
if(len[np]==len[p]+1) fa[q]=np;
else
{
int nq=++cnt;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[np],sizeof(ch[nq]));
fa[nq]=fa[np];
fa[np]=fa[q]=nq;
for(;p && ch[p][c]==np;p=fa[p]) ch[p][c]=nq;
}
}
}
char s[N];
int id[N];ll f[N];
int main()
{
int n;
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++) insert(s[i]-'a');
for(int i=1;i<=cnt;i++) id[i]=i;
sort(id+1,id+cnt+1,[&](int x,int y){return len[x]<len[y];});
for(int i=cnt;i;i--)
{
int u=id[i];
for(int c=0;c<26;c++) f[u]=max(f[u],f[ch[u][c]]+siz[ch[u][c]]);
if(u==1) break;
siz[fa[u]]+=siz[u];
f[fa[u]]=max(f[fa[u]],f[u]+1ll*(len[u]-len[fa[u]])*siz[u]);
}
printf("%lld\n",f[1]);
return 0;
}
E. Guess Me If You Can
交互题。
考虑如果把所有位置 \(+1\),那么等于没有操作。
所以考虑每次加一个排列。如果某一时刻 \(x_i\) 加上 \(1\) 之后,本质不同数量变少了,那么 \(x_i+1\) 一定有值。而所有元素互不相同,所以 \(x_i\) 一定不是最大的值。
考虑每次变少的概率是 \(\frac 13\),事实上操作若干次之后概率可以忽略不计。
操作复杂度 \(O(n\log n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define N 1010
using namespace std;
int p[N];
bool f[N];
int main()
{
srand(1019260817);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) p[i]=i;
for(int _=1;;_++)
{
random_shuffle(p+1,p+n+1);
int w=n;
for(int i=1;i<=n;i++)
{
printf("0 %d\n",p[i]);fflush(stdout);
int v;
scanf("%d",&v);
if(v<w) f[p[i]]=true;
w=v;
}
int c=0;
for(int i=1;i<=n && c>=0;i++)
if(!f[i]){if(c==0) c=i;else c=-1;}
if(c!=-1){printf("1 %d\n",c);return 0;}
}
return 0;
}
F. Lazy Hash Table
考虑一个模数 \(m\) 合法,当且仅当 \(\forall i< j\ ,\ m\nmid|a_i-a_j|\)。
那么直接将 \(a\) 自己对自己做减法卷积,用 FFT 实现,复杂度 \(O(n\log n)\)。
最后部分直接枚举 \(m\) 然后调和级数复杂度判断,复杂度 \(O(n\log n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 8000010
using namespace std;
namespace NTT{
const int mod=998244353,G=3,Gi=(mod+1)/G;
int ksm(int a,int b=mod-2)
{
int r=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
return r;
}
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int rev[N];
int get_rev(int n)
{
int lim=1,l=0;
while(lim<=n) lim<<=1,l++;
for(int i=1;i<lim;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
return lim;
}
void ntt(int f[],int lim,int op=1)
{
for(int i=1;i<lim;i++)
if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int mid=1;mid<lim;mid<<=1)
{
int r=ksm(op==1?G:Gi,(mod-1)/(mid*2));
for(int i=0;i<lim;i+=mid<<1)
for(int j=0,o=1;j<mid;j++,o=1ll*o*r%mod)
{
int x=f[i+j],y=1ll*f[i+j+mid]*o%mod;
f[i+j]=add(x,y);f[i+j+mid]=dec(x,y);
}
}
if(op==-1)
{
int r=ksm(lim);
for(int i=0;i<lim;i++) f[i]=1ll*f[i]*r%mod;
}
}
void mul(int f[],int g[],int n)
{
int lim=get_rev(n*2);
ntt(f,lim,1);ntt(g,lim,1);
for(int i=0;i<lim;i++) f[i]=1ll*f[i]*g[i]%mod;
ntt(f,lim,-1);
}
}
using NTT::mul;
int a[N],b[N];
int main()
{
int n;
scanf("%d",&n);
int m=2000001;
for(int i=1,x;i<=n;i++) scanf("%d",&x),a[x]++,b[m-x]++;
mul(a,b,m);
for(int i=1;i<=m;i++) a[i]=a[i+m];
// for(int i=1;i<=m*2;i++) printf("%d ",a[i]);
// puts("");
for(int x=1;x<=m;x++)
{
bool can=true;
for(int i=1;i*x<=m;i++) if(a[i*x]) {can=false;break;}
if(can){printf("%d\n",x);return 0;}
}
return 0;
}

浙公网安备 33010602011771号