IOI2018

【IOI2018】组合动作(构造)

一种思路是:注意到后面的字符都和第一个字符不同,于是对每一位进行2次询问(不用3次,因为剩下的字符可直接确定)确定字符串,不太理想。

但是给询问的字符串长度<=4*n,这启示我们进行并行询问。实际上,假设现在答案字符串为S,先找到首位字符,询问$SAASABSACSB$(A,B,C是除了首位字符的字符)就能知道下一位的结果。(可以看代码)最后一个暴力即可。

但是首位字符需要3次询问,可以二分一下字符集,就可以降到2次询问。

#include 
#include "combo.h"
using namespace std;
#define p press
string guess_sequence(int n) {
    string r[3], a;
    if (p("AB")) {
        if (p("A")) {
            r[0] = "B";
            r[1] = "X";
            r[2] = "Y";
            a = "A";
        } else {
            r[0] = "A";
            r[1] = "X";
            r[2] = "Y";
            a = "B";
        }
    } else {
        if (p("X")) {
            r[0] = "A";
            r[1] = "B";
            r[2] = "Y";
            a = "X";
        } else {
            r[0] = "A";
            r[1] = "B";
            r[2] = "X";
            a = "Y";
        }
    }
    if(n==1)return a;
    for (int i = 2; i < n ; i++) {
        int v = p(a + r[0] + a + r[1] + r[0] + a + r[1] + r[2] + a + r[1] + r[1]);
        if (v == i+1)
            a += r[1];
        else if (v == i)
            a += r[0];
        else
            a += r[2];
    }
    if (p(a + r[0])==n)
        a += r[0];
    else if (p(a + r[1])==n)
        a += r[1];
    else
        a += r[2];
    return a;
}
View Code

代码不合我的码风是因为在loj上格式化了。

【IOI2018】排座位(线段树)

这道题不能用$\max-\min=r-l$的传统思路。

先考虑$H=1$时怎么做。在线段树上每个叶子节点都维护"把当前值染黑,有多少个黑色连续段",则答案就是区间最小值个数。(只有0组成的区间显然满足条件,节点的值为1(也是最小值),所以询问区间最小值可以得到正确结果)

把位置相邻的点连边。则每个位置的值是点-边数。

考虑交换操作。由于在区间$[1,l]$和$[r,w]$的节点,这两个点同时被染白/染黑,所以不用计算贡献。如果点在区间$[l,r]$则要计算贡献。可以发现节点的变化情况都是$l$变白,$r$变黑,都是反转颜色。

可以把原来的贡献删除并加入现在的贡献。具体来说,假设一个黑变白的点是$x$旁边是$y$则当$x$是黑的且$y$是白的时候要删除贡献。就是一个区间减法。加入贡献同理。

回到原题,在线段树上每个叶子节点都维护"把当前值染黑,有多少个黑色矩形",则答案就是区间最小值个数。(只有0组成的区间显然满足条件,节点的值为1)

实际上,这个连续段的个数等于黑点挨着>=2的白点的点个数+白点挨着超过2个黑点的点的个数。

在加入黑点时可以删除原来的贡献并且加入新的贡献。方法类似。

#include
using namespace std;
#define N 4000010
int mn[N],s[N],va[N],bz[N],n,m,mt[N],v,q,ss[N];
#define id(x,y) (x-1)*m+y
void bd(int o,int l,int r){
    if(l==r){
        mn[o]=va[l];
        s[o]=1;return;
    }
    int md=(l+r)/2;
    bd(o*2,l,md);
    bd(o*2+1,md+1,r);
    mn[o]=min(mn[o*2],mn[o*2+1]);
    s[o]=0;
    if(mn[o]==mn[o*2])s[o]+=s[o*2];
    if(mn[o]==mn[o*2+1])s[o]+=s[o*2+1];
}
void pd(int o){
    bz[o*2]+=bz[o];
    bz[o*2+1]+=bz[o];
    mn[o*2]+=bz[o];
    mn[o*2+1]+=bz[o];
    bz[o]=0;
}
void mod(int o,int l,int r,int x,int y,int v){
    if(x>y||rreturn;
    if(x<=l&&r<=y){
        mn[o]+=v;
        bz[o]+=v;
        return;
    }
    pd(o);
    int md=(l+r)/2;
    mod(o*2,l,md,x,y,v);
    mod(o*2+1,md+1,r,x,y,v);
    mn[o]=min(mn[o*2],mn[o*2+1]);
    s[o]=0;
    if(mn[o]==mn[o*2])s[o]+=s[o*2];
    if(mn[o]==mn[o*2+1])s[o]+=s[o*2+1];
}
struct no{
    int x,y;
}a[N];
int tx[4]={0,-1,0,1},ty[4]={-1,0,1,0};
int in(int x,int y){return x>0&&x<=n&&y>0&&y<=m;}
int m1(int x){
    int mn=v+1;
    if(in(tx[0]+a[x].x,ty[0]+a[x].y))
        mn=min(mn,mt[id(tx[0]+a[x].x,ty[0]+a[x].y)]);
    if(in(tx[1]+a[x].x,ty[1]+a[x].y))
        mn=min(mn,mt[id(tx[1]+a[x].x,ty[1]+a[x].y)]);
    return mn;
}
int m2(int x){
    int r1=v+1,r2=v+1;
    for(int i=0;i<4;i++)
        if(in(tx[i]+a[x].x,ty[i]+a[x].y)){
            if(mt[id(tx[i]+a[x].x,ty[i]+a[x].y)]<r1)
                r2=r1,r1=mt[id(tx[i]+a[x].x,ty[i]+a[x].y)];
            else if(mt[id(tx[i]+a[x].x,ty[i]+a[x].y)]<r2)
                r2=mt[id(tx[i]+a[x].x,ty[i]+a[x].y)];
        }
    return r2;
}
int main(){
    scanf("%d%d%d",&n,&m,&q);
    v=n*m;
    for(int i=1;i<=v;i++){
        scanf("%d%d",&a[i].x,&a[i].y);
        a[i].x++;
        a[i].y++;
        mt[id(a[i].x,a[i].y)]=i;
    }
    for(int i=1;i<=v;i++){
        va[i]=va[i-1];
        if(m2(i);
        if(m1(i)>i)va[i]++;
        for(int j=0;j<4;j++)
            if(in(tx[j]+a[i].x,ty[j]+a[i].y)){
                int v=mt[id(tx[j]+a[i].x,ty[j]+a[i].y)];
                if(v;
                else if(v>i&&m2(v)==i)va[i]++;
            }
    }
    bd(1,1,v);
    while(q--){
        int x,y,ct=0;
        scanf("%d%d",&x,&y);
        x++;
        y++;
        if(x>y)swap(x,y);
        if(x==y){
            printf("%d",s[1]);
            printf("\n");
            continue;
        }
        ss[++ct]=x;ss[++ct]=y;
        for(int i=0;i<4;i++)
            if(in(tx[i]+a[x].x,ty[i]+a[x].y))
                ss[++ct]=mt[id(tx[i]+a[x].x,ty[i]+a[x].y)];
        for(int i=0;i<4;i++)
            if(in(tx[i]+a[y].x,ty[i]+a[y].y))
                ss[++ct]=mt[id(tx[i]+a[y].x,ty[i]+a[y].y)];
        sort(ss+1,ss+ct+1);
        for(int i=1;i<=ct;i++)
            if(ss[i]!=ss[i-1]){
                if(m2(ss[i])<ss[i])
                    mod(1,1,v,max(m2(ss[i]),x),min(ss[i],y)-1,-1);
                if(m1(ss[i])>ss[i])
                    mod(1,1,v,max(ss[i],x),min(m1(ss[i]),y)-1,-1);
            }
        swap(mt[id(a[x].x,a[x].y)],mt[id(a[y].x,a[y].y)]);
        swap(a[x],a[y]);
        for(int i=1;i<=ct;i++)
            if(ss[i]!=ss[i-1]){
                if(m2(ss[i])<ss[i])
                    mod(1,1,v,max(m2(ss[i]),x),min(ss[i],y)-1,1);
                if(m1(ss[i])>ss[i])
                    mod(1,1,v,max(ss[i],x),min(m1(ss[i]),y)-1,1);
            }
        printf("%d",s[1]);
        printf("\n");
    }
}
View Code

 

【IOI2018】狼人(可持久化线段树,Kruskal重构树,dfs序)

由于在人形态不能经过$

同理可以建出边权为$\min(x,y)$(x,y为边的两端点标号)的kruskal重构树。

在判定是否能变身时,可以在重构树上dfs一下得到dfs序,把所有点视为平面上的一个点$(dfn1_x,dfn2_x)$,则查询时倍增到对应的点,则倍增到的位置的子树的所有节点是当前点可以走的。判定是否有点在这2个区间构成的矩形即可。数点可用离线+BIT/可持久化线段树解决

#include
using namespace std;
#define N 800010
int n,m,q,rt[N],lc[N<<4],rc[N<<4],sz[N<<4],ct,va[N];
struct ed{
    int a,b,c;
}e[N];
int operator <(ed x,ed y){return x.c<y.c;}
int cp(ed x,ed y){return x.c>y.c;}
void mod(int &o,int p,int l,int r,int x){
    if(!o)o=++ct;
    sz[o]=sz[p]+1;
    if(l==r)return;
    int md=(l+r)/2;
    if(x<=md){
        rc[o]=rc[p];
        mod(lc[o],lc[p],l,md,x);
    }
    else{
        lc[o]=lc[p];
        mod(rc[o],rc[p],md+1,r,x);
    }
}
int qu(int o,int p,int l,int r,int x,int y){
    if(!o||rreturn 0;
    if(x<=l&&r<=y)return sz[o]-sz[p];
    int md=(l+r)/2;
    return qu(lc[o],lc[p],l,md,x,y)+qu(rc[o],rc[p],md+1,r,x,y);
}
struct no{
    int f[N][20],p[N],ct,a[N],ec,dfn[N],cc,v[N*2],nxt[N*2],h[N],rt,d1[N],d2[N];
    ed d[N];
    void add(int x,int y){v[++ec]=y;nxt[ec]=h[x];h[x]=ec;}
    int fd(int x){return p[x]?p[x]=fd(p[x]):x;}
    void bd(){
        ct=n;
        for(int i=1;i<=n;i++)a[i]=i;
        for(int i=1;i<=m;i++){
            int xx=fd(d[i].a),yy=fd(d[i].b);
            if(xx!=yy){
                ct++;
                a[ct]=d[i].c;
                f[xx][0]=ct;
                f[yy][0]=ct;
                p[xx]=p[yy]=ct;
                add(ct,xx);
                add(ct,yy); 
                rt=ct;
            }
        }
        for(int i=1;i<20;i++)
            for(int j=1;j<=ct;j++)
                f[j][i]=f[f[j][i-1]][i-1];
    }
    void dfs(int x,int t){
        if(x<=n)dfn[x]=++cc;
        for(int i=h[x];i;i=nxt[i])
            dfs(v[i],t);
        if(x>n)d1[x]=1e9;
        else d2[x]=d1[x]=dfn[x];
        for(int i=h[x];i;i=nxt[i]){
            d2[x]=max(d2[x],d2[v[i]]);
            d1[x]=min(d1[x],d1[v[i]]);
        }
    }
    void init(int t){
        if(t){
            for(int i=1;i<=m;i++){
                d[i]=e[i];
                d[i].c=min(e[i].a,e[i].b);
            }
            sort(d+1,d+m+1,cp);
        }
        else{
            for(int i=1;i<=m;i++){
                d[i]=e[i];
                d[i].c=max(e[i].a,e[i].b);
            }
            sort(d+1,d+m+1);
        }
        bd();
        dfs(rt,t);
    }
    int bz(int x,int t,int v){
        for(int i=19;~i;i--){
            if(t&&a[f[x][i]]>=v&&f[x][i])x=f[x][i];
            else if(!t&&a[f[x][i]]<=v&&f[x][i])x=f[x][i];
        }
        return x;
    }
}x,y;
int main(){
    cin>>n>>m>>q;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&e[i].a,&e[i].b);
        e[i].a++;
        e[i].b++;
    }
    x.init(1);
    y.init(0);
    for(int i=1;i<=n;i++)
        va[x.dfn[i]]=i;
    for(int i=1;i<=n;i++)
        mod(rt[i],rt[i-1],1,y.cc,y.dfn[va[i]]);
    while(q--){
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        a++;b++;c++;d++;
        int p1=x.bz(a,1,c),p2=y.bz(b,0,d);
        int l1=x.d1[p1],r1=x.d2[p1];
        int l2=y.d1[p2],r2=y.d2[p2];
        if(qu(rt[r1],rt[l1-1],1,n,l2,r2))puts("1");
        else puts("0");
    }
}
View Code

 

【IOI2018】机械娃娃(线段树,构造)

题目中给了$2$-开关,可以考虑把它扩展到$k$-开关

可以使用线段树构造。构造一颗线段树,大小为$m$(m为最小的$2$的次幂使其>=关键点个数)。叶子节点连向对应的关键点,如果一个点没有连向关键点(叶子节点超出需求),就把它连向根。这样子最坏情况要$2*n$个开关,不太理想。

注意到,如果一个点子树的所有叶子节点都被连到根,则可以把这个子树除当前节点的其他节点删除,并且把当前点连向根。但是由于访问到的顺序是fft的rev数组,复杂度没有保证,连的效果不好。

实际上,可以把线段树的右边n-关键点个节点空出来,类似区间定位一样找到$\log_2 n$个节点,把它们的子树都删掉,把这$\log_2 n$个节点都连到根。

没被空出来的节点按顺序连到关键点即可。这样子就可以把开关数降到$n+\log_2 n$

#include
#include "doll.h"
using namespace std;
#define N 300010
vector<int>c,x,y;
int n,p=1,l,rv[N],tv[N],tp[N];
int bd(int l,int r){
    if(l==r)return l>=p-n?tv[l]:-p;
    int md=(l+r)/2;
    int lc=bd(l,md),rc=bd(md+1,r);
    if(lc==-p&&rc==-p)return -p;
    x.push_back(lc);
    y.push_back(rc);
    return -x.size();
}
void create_circuit(int m,vector<int>v){
    n=v.size();
    v.push_back(0);
    while(p<=n)p*=2,l++;
    for(int i=0;i)
        rv[i]=(rv[i>>1]>>1)|((i&1)<<(l-1));
    memset(tp,127,sizeof(tp));
    for(int i=p-n;ii;
    int ct=0;
    for(int i=0;i)
        if(tp[i]<2e9)tv[tp[i]]=v[++ct];
    int rt=bd(0,p-1);
    c.push_back(v[0]);
    for(int i=0;i)
        c.push_back(rt);
    for(auto &i:x)if(i==-p)i=rt;
    for(auto &i:y)if(i==-p)i=rt;
    answer(c,x,y);
}
View Code

 

【IOI2018】高速公路收费(二分,最短路)

先询问一下得到原图的最短路。

考虑在图上二分出一条最短路的边。(不能二分出点,否则只能拿最多$90$分。如果二分出点拿到$100$分请告诉笔者。)

二分的方法是:(下文设md为区间终点)把左端点$l$到$md$的边设为拥堵边,检测询问的值是否等于原图的最短路。如果是则右移左端点否则左移右端点。

如果把$[l,md]$设为拥堵边后询问的值不是最短路,则所有最短路上的边都在$[l,md]$里面。

如果把$[l,md]$设为拥堵边后询问的值是最短路,则不是所有最短路的边都在$[l,md]$中。

(咕咕咕)

代码不合我的码风是因为在loj上格式化了。

【IOI2018】会议(线段树,dp)

 由题意可以得到一个dp方程:设$f_{l,r}$表示$[l,r]$区间最小代价,由题意$f_{l,r}=\min(f_{l,md-1}+(r-md+1)*h_{md},f_{md+1,r}+(md-l+1)*h_{md})$,其中$md$为最小值所在位置。

但是这样子效率太低。只能过$19$分。不能直接把$dp$数组计算出来,而要根据询问减少计算量。

根据套路考虑建出原序列的笛卡尔树,可以用rmq建,然后把每一个询问挂在笛卡尔树对应的点上。这样子的好处是每次处理的$mid$都是一定的。

对于询问$[l,r]$只和$f_{l,md-1}$和$f_{md+1,r}$有关。且这2个dp数组有一个端点是$md-1$或$md+1$。对于下面的子任务,笛卡尔树不高,所以可以暴力算。这样子可以拿到$60$分。

所以可以考虑用线段树维护$f_{l...md-1,md-1}$和$f_{md+1...r,r}$的值($l,r$是当前分治区间不是询问区间),对于每一个询问单点询问就可以直接得到答案。

考虑做完当前点后维护$f_{l...r,r}$和$f_{l,l...r}$的值。考虑计算$f_{l...r,r}$的值的过程。可以观察原dp方程,可以注意到当$r$向右移一位时,左边的项会增加$h_{md}$,右边的项会增加$f_{md+1,r}-f_{md+1,r-1}$。一边的贡献是一次函数,但是可以不用李超树维护。由于笛卡尔树的性质,区间$[md+1,r]$的最小值小于等于$md$的最小值。所以右边的增量$\leq md$。所以方程取左边还是右边有一个分界线,可以二分出这个分界线然后更新线段树(就是区间一次函数覆盖/区间加)。(其实不用二分。只要维护线段树左/右端点处的值,当左边比右边端点取方程同一边时直接改,否则继续递归)。

然后上传到上面的区间。这样子上传没有问题。先讨论一下以当前右端点为$r$的dp数组(左端点为$l$的数组相似),是因为如果新的最大值位置是$r+1$,则现在更新的dp值恰好可以用于更新上面的询问。如果新的最大值位置为$l-1$,则不用更新上面的询问。由于笛卡尔树上当前点的子节点已经被算完了,所以不会对当前点的子节点产生影响。

自此,我们在$O(n\log_2 n)$的时间内离线解决了这道题。如果硬要在线只要把线段树可持久化一下即可。

#include
using namespace std;
typedef long long ll;
#define N 750010
struct st{
    ll ck[N<<2],cb[N<<2],ad[N<<2],vl[N<<2],vr[N<<2],cc[N<<2];
    void cv(int o,int l,int r,ll k,ll b){
        cc[o]=1;
        ad[o]=0;
        vl[o]=k*l+b;
        vr[o]=k*r+b;
        ck[o]=k;
        cb[o]=b;
    }
    void av(int o,ll v){
        ad[o]+=v;
        vl[o]+=v;
        vr[o]+=v;
    }
    void pd(int o,int l,int r){
        int md=(l+r)/2;
        if(cc[o]){
            cv(o*2,l,md,ck[o],cb[o]);
            cv(o*2+1,md+1,r,ck[o],cb[o]);
            cc[o]=0;
        }
        if(ad[o]){
            av(o*2,ad[o]);
            av(o*2+1,ad[o]);
            ad[o]=0;
        }
    }
    ll q(int o,int l,int r,int x){
        if(l==r)return vl[o];
        int md=(l+r)/2;
        pd(o,l,r);
        ll rr=(x<=md)?q(o*2,l,md,x):q(o*2+1,md+1,r,x);
        return rr;
    }
    void mod(int o,int l,int r,int x,int y,ll k,ll b,ll v){
        if(rreturn;
        if(x<=l&&r<=y){
            if(k*r+b<=vr[o]+v&&k*l+b<=vl[o]+v){
                cv(o,l,r,k,b);
                return;
            }
            if(k*r+b>=vr[o]+v&&k*l+b>=vl[o]+v){
                av(o,v);
                return;
            }
        }
        pd(o,l,r);
        int md=(l+r)/2;
        mod(o*2,l,md,x,y,k,b,v);
        mod(o*2+1,md+1,r,x,y,k,b,v);
        vl[o]=vl[o*2];
        vr[o]=vr[o*2+1];
    }
}fl,fr;
int n,q,lg[N],f[N][20],l[N],r[N];
vector<int>v[N];
ll a[N],h[N];
int mm(int x,int y){
    return h[x]>h[y]?x:y;
}
int qu(int l,int r){
    int v=lg[r-l+1];
    return mm(f[l][v],f[r-(1<1][v]);
}
void dfs(ll x,ll y){
    if(x>y)return;
    ll md=qu(x,y);
    ll l1=0,r1=0;
    dfs(x,md-1);dfs(md+1,y);
    for(auto i:v[md]){
        a[i]=(ll)h[md]*(r[i]-l[i]+1);
        if(l[i]1)*h[md]+fr.q(1,1,n,l[i]));
        if(r[i]>md)a[i]=min(a[i],(ll)(md-l[i]+1)*h[md]+fl.q(1,1,n,r[i]));
    }
    if(x1,1,n,x);
    if(y>md)r1=fl.q(1,1,n,y);
    fr.mod(1,1,n,x,md,-h[md],r1+(ll)h[md]*(md+1),(ll)h[md]*(y-md+1));
    fl.mod(1,1,n,md,y,h[md],l1-(ll)h[md]*(md-1),(ll)h[md]*(md-x+1));
}
int main(){
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
        scanf("%d",&h[i]);
    for(int i=2;i)
        lg[i]=lg[i>>1]+1;
    for(int i=1;i<=n;i++)
        f[i][0]=i;
    for(int i=1;i<20;i++)
        for(int j=1;j+(1<<(i))-1<=n;j++)
            f[j][i]=mm(f[j][i-1],f[j+(1<<(i-1))][i-1]);
    for(int i=1;i<=q;i++){
        scanf("%d%d",&l[i],&r[i]);
        l[i]++;r[i]++;
        int md=qu(l[i],r[i]);
        v[md].push_back(i);
    }
    dfs(1,n);
    for(int i=1;i<=q;i++)
        printf("%lld\n",a[i]);
}
View Code

 

你可能感兴趣的:(IOI2018)