zoukankan      html  css  js  c++  java
  • IOI2018

    【IOI2018】组合动作(构造)

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

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

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

    #include <bits/stdc++.h>
    #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<bits/stdc++.h>
    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||r<x||y<l)return;
        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)<i)va[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<i&&m1(v)==i)va[i]--;
                    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("
    ");
                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("
    ");
        }
    }
    View Code

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

    由于在人形态不能经过$<l$的点,所以经过的点的标号最小值要$geq l$。可以考虑建出边权为$max(x,y)$(x,y为边的两端点标号)的kruskal重构树。

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

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

    #include<bits/stdc++.h>
    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||r<x||y<l)return 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<bits/stdc++.h>
    #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<p;i++)
            rv[i]=(rv[i>>1]>>1)|((i&1)<<(l-1));
        memset(tp,127,sizeof(tp));
        for(int i=p-n;i<p;i++)tp[rv[i]]=i;
        int ct=0;
        for(int i=0;i<p;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<m;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(nlog_2 n)$的时间内离线解决了这道题。如果硬要在线只要把线段树可持久化一下即可。

    #include<bits/stdc++.h>
    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(r<x||y<l)return;
            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<<v)+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]<md)a[i]=min(a[i],(ll)(r[i]-md+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(x<md)l1=fr.q(1,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<N;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
    ",a[i]);
    }
    View Code
  • 相关阅读:
    mdk3 工具使用-表白神器
    Crunch黑客神器-创造个性字典
    centos 自动挂载ISO
    渗透测试工具Nmap从初级到高级
    mui 点击长按复制文本
    JavaScript倒计时并刷新页面
    javascript单一复制粘贴
    jquery定义链接跳转的高亮显示
    JS判断移动端访问设备并加载对应CSS样式
    jquery刷新数据随机排列
  • 原文地址:https://www.cnblogs.com/cszmc2004/p/12258837.html
Copyright © 2011-2022 走看看