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 一定有交。考虑此时合法的情况,此时交点处是没有点的,也就是两种中的一种:

image

首先对于判断合法性,显然直接求出两种情况下序列长度之和的最大值,判断是否和 LIS 长度加 LDS 长度之和相同。

考虑如何求出序列长度之和最大值。首先考虑左边的情况,我们枚举蓝线的左端点,那么蓝线一定会找到一个尽可能小的右端点。

考虑此时的交点落在的区域 \([x,x+1],[y,y+1]\)

image

那么其左下,右上是可行的红色区域,反之同理。

以交点(黄色点)纵坐标建线段树,横坐标作扫描线。加入一条蓝线的贡献。

假如已经得到了原序列 LDS,注意由于这里是贪心取的,故每个位置都是尽可能大的值,故 \(\geq p\) 的 LDS 长度等同于 LDS 序列中 \(\geq p\) 的位置数。

考虑什么情况下会不合法:
image

加入一个点后可能有一部分点,不符合条件,有一部分点变多了,但是可以发现这对于任意一种颜色,只会导致答案 \(+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;
}
posted @ 2021-04-23 17:56  Flying2018  阅读(109)  评论(0)    收藏  举报