【SCOI补全记】SCOI2015 bzoj4443-4448

版权声明:转载请附带链接或评论 https://blog.csdn.net/corsica6/article/details/82710750


T1:小凸玩矩阵(bzoj4443)

题解

关键词:二分 二分图匹配

最大匹配都不需要(雾?)以行列建二分图,第 i 行代表节点到第 j 列连一条权值为 a i , j 的边。

二分答案,每次只连权值 m i d 的边,判断最大匹配 k

代码没有难度,但需要一定思维时间。感觉还是挺妙的。二分图匹配的实际复杂度一直很玄。

代码

#include<bits/stdc++.h>
#define RI register
using namespace std;
const int N=255;
int a[N][N],bel[N<<1],n,m,K,mx,mi=2e9,L,R,mid,ans;
int head[N<<1],to[N*N*2],nxt[N*N*2],tot,vis[N<<1],tim;

inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}

inline bool dfs(int x)
{
    RI int i,j;
    for(i=head[x];i;i=nxt[i]){
        j=to[i];if(vis[j]==tim) continue;
        vis[j]=tim;
        if(!bel[j] || dfs(bel[j])){
            bel[j]=x;return true;
        }
    }
    return false;
}

inline int maxmatch()
{
    int re=0;RI int i;
    for(i=1;i<=n;++i) {
      tim++;
      if(dfs(i)) re++;  
    }
    return re;
}

inline bool check(int lim)
{
    RI int i,j;
    memset(head,0,sizeof(head));
    memset(bel,0,sizeof(bel));
    tot=0;
    for(i=1;i<=n;++i)
     for(j=1;j<=m;++j) if(a[i][j]<=lim)
         lk(i,j+n);
    return maxmatch()>=K;
}

int main(){
    RI int i,j;
    scanf("%d%d%d",&n,&m,&K);
    K=n-K+1;
    for(i=1;i<=n;++i)
        for(j=1;j<=m;++j){
            scanf("%d",&a[i][j]);
            mx=max(mx,a[i][j]);
            mi=min(mi,a[i][j]);
        }
    L=mi;R=mx;
    for(;L<=R;){
        mid=(L+R)>>1;
        if(check(mid)) R=(ans=mid)-1;
        else L=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

T2:国旗计划(bzoj4444)

题解

关键词:倍增 贪心

区间不相互包含,所以不会有两个人起点相同,而对于 l i < l j ,必有 r i < r j ,按 l 升序排序后, l , r 是同时单增的。考虑走最少的步数,而显然每次 i 贪心直接转移到的最远的位置就是满足有 l k r i 的最大的 k r k

因为对每个人都要回答一次询问,所以就存一个倍增数组,为方便统计,设从 i 点出发跳 2 j 1 次到达的位置 f i , j ( f i 0 = i )。直接处理即可。

代码有些小技巧,注意要破环为链。时间复杂度 O ( n log n )

代码

#include<bits/stdc++.h>
#define RI register
using namespace std;
const int N=4e5+10,w=18;

int n,m,f[N][20],to[N],ans[N],tot,bin[25];

struct P{ 
  int l,r,id;
  P(){}
  P(int LL,int RR,int DI){l=LL;r=RR;id=DI;}
}t[N];

inline bool cmp(const P&A,const P&B){return A.l<B.l;}

int main(){
    RI int i,j,x,y;
    bin[0]=1;for(i=1;i<=20;++i) bin[i]=bin[i-1]<<1;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i){
        scanf("%d%d",&x,&y);
        if(x<=y){t[++tot]=P(x,y,i);t[++tot]=P(x+m,y+m,i);}
        else t[++tot]=P(x,y+m,i);
    }
    sort(t+1,t+tot+1,cmp);
    t[++tot].r=m*2+1;
    for(j=1,i=1;i<=tot;++i){
      for(;j<tot && t[j+1].l<=t[i].r;++j);
      f[i][0]=i;to[i]=j;
    }
    for(i=1;i<=tot;++i)
     for(j=1;j<=w;++j)
      f[i][j]=tot;
    for(j=1;j<=w;++j)
     for(i=1;i<=tot;++i)
      f[i][j]=f[to[f[i][j-1]]][j-1];
    for(i=1;i<tot;++i) if(!ans[t[i].id]){
       x=i;y=t[i].l+m;
       for(j=w;~j;--j) if(t[f[x][j]].r<y) 
         x=to[f[x][j]],ans[t[i].id]|=bin[j];
    }
    for(i=1;i<=n;++i) printf("%d ",ans[i]+1);
    return 0;
}

T3:小凸想跑步(bzoj4445)

关键词:半平面交
传送门


T4:小凸玩密室(bzoj4446)

关键词: 树形DP
传送门


T5:小凸解密码(bzoj4447)

题解

关键词:平衡树/set

破环为链。

s e t 中存储的元素下标表示当前一段 0 区间的起点,值表示当前一段 0 区间的终点,将 B i 依次插入 s e t 中,每次合并相邻的零区间即可。

注意到每次询问只需要把 s e t b p o s 改为 a p o s ,然后在 p o s + n 2 左右拓展几次找最远的 0 区间即可。

每次修改操作同样也只会影响到 b p o s , b p o s + 1

s e t 用得还是不太熟。

代码

#include<bits/stdc++.h>
#define RI register
#define pr(x) ((x)==1?n:x-1)
#define nt(x) ((x)==n?1:x+1)
#define fi first
#define sc second
#define it map<int,int>::iterator
using namespace std;
const int N=1e5+10;

int n,m,a[N],b[N],c[N],nz[N];
char s[3];
map<int,int>S; 

inline it pre(it x){return --x;}
inline it nxt(it x){return ++x;}

inline it fd(int x)
{
    it re=--S.upper_bound(x);
    if(re->fi!=x) 
    {S[x]=re->sc;S[re->fi]=x-1;re=S.lower_bound(x);}
    if(re->sc!=x) 
    {S[x+1]=re->sc;S[x]=x;re=S.lower_bound(x);}
    return re;
}

inline void merge(int x)
{
    it re=S.lower_bound(x);
    if(pre(re)->fi>0 && pre(re)->sc==re->fi-1)
    {re=pre(re);S[re->fi]=x;S.erase(nxt(re));}
    if(nxt(re)->fi<=n && nxt(re)->fi==re->sc+1)
    {S[re->fi]=nxt(re)->sc;S.erase(nxt(re));}
}

inline void modify(int x,int vv)
{
    if(vv==nz[x])return;
    if(vv && nz[x]==0) S.erase(fd(x));
    else if(nz[x] && vv==0){S[x]=x;merge(x);}
    nz[x]=vv;
}

inline bool in(int l,int r,int x)
{
    if(l<=r) return (l<=x && x<=r);
    return (x<=r || x>=l);
}

inline int cal(int l,int r,int x)
{
   if(in(l,r,x)) return 0;
   if(l<=r){
     if(x<l) return min(l-x,x+n-r);
     return min(x-r,l+n-x);
   }
   return min(l-x,x-r);
}

inline int get(int x)
{
    modify(x,a[x]);
    if(S.size()==2) {modify(x,b[x]);return -1;}
    RI int i,l,r,rs=x+n/2;int ret=0;if(rs>n) rs-=n;
    it re=--S.upper_bound(rs),temp;
    if(re==--S.end()) --re;
    if(re==S.begin()) ++re;
    for(i=1;i<=3;++i) re= re==++S.begin()? --(--S.end()):--re;
    for(i=1;i<=6;++i){
        l=re->fi,r=re->sc;
        if(l!=1 && r==n){
            temp=++S.begin();
            if(temp->fi==1) r=temp->sc;
        }
        if(l==1 && r!=n){
            temp=--(--S.end());
            if(temp->sc==n) l=temp->fi;
        }
        ret=max(ret,cal(l,r,x));
        re= re==--(--S.end())? ++S.begin():++re;
    } 
    modify(x,b[x]);
    return ret;
}

int main(){
    RI int i,j,op,x,y;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i){
        scanf("%d%s",&a[i],s);
        c[i]=s[0]=='+'? 1:0;
    }
    a[0]=a[n];
    for(i=1;i<=n;++i){
        if(c[i]) b[i]=(a[i]+a[i-1])%10;
        else b[i]=a[i]*a[i-1]%10;
    }
    S[0]=0;S[n+1]=n+1;
    for(i=1;i<=n;++i){
        nz[i]=1;modify(i,b[i]);
    }
    for(i=1;i<=m;++i){
        scanf("%d%d",&op,&x);
        x++;
        if(op==1){
           scanf("%d%s",&y,s);
           c[x]=s[0]=='+'? 1:0;
           a[x]=y;
           if(c[x]) b[x]=(a[x]+a[pr(x)])%10;
           else b[x]=a[x]*a[pr(x)]%10;
           modify(x,b[x]);
           if(c[nt(x)]) b[nt(x)]=(a[nt(x)]+a[x])%10;
           else b[nt(x)]=a[nt(x)]*a[x]%10;
           modify(nt(x),b[nt(x)]);
        }else printf("%d\n",get(x));
    }
    return 0;
} 

T6:情报传递(bzoj4448)

题解

关键词:主席树 树链剖分

题意还是有点难理解…

实际上是对每次操作建主席树,每次选择一条链,问链上在 t c 1 次操作及之前被安排过搜集情报的点的个数。

清楚了问题的本质之后,以dfs序为下标建一颗操作时间主席树即可。

代码

#include<bits/stdc++.h>
#define mid (((l)+(r))>>1)
#define RI register
using namespace std;

const int N=2e5+10;

int n,m,f[N],root,in[N],dfn;
int rt[N],ls[N*20],rs[N*20],ss[N*20],cnt;
int d[N],top[N],son[N],sz[N],vs[N];
int head[N],to[N],nxt[N],tot;

inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}

inline void dfs(int x)
{
    RI int i,j;sz[x]=1;
    for(i=head[x];i;i=nxt[i]){
        j=to[i];d[j]=d[x]+1;
        dfs(j);sz[x]+=sz[j];
        if(sz[j]>sz[son[x]]) son[x]=j;
    }
}

inline void df(int x,int tp)
{
    top[x]=tp;
    in[x]=++dfn;
    if(!son[x]) return;
    df(son[x],tp);
    RI int i,j;
    for(i=head[x];i;i=nxt[i]){
        j=to[i];if(j==son[x]) continue;
        df(j,j);
    }
}

inline void ins(int pre,int &nw,int l,int r,int pos)
{
    nw=++cnt;ss[nw]=ss[pre]+1;
    if(l==r) return;
    if(pos<=mid){
        rs[nw]=rs[pre];
        ins(ls[pre],ls[nw],l,mid,pos);
    }else{
        ls[nw]=ls[pre];
        ins(rs[pre],rs[nw],mid+1,r,pos);
    }
}

inline int get(int k,int l,int r,int L,int R)
{
    if(!k) return 0;
    if(L<=l && r<=R) return ss[k];
    int re=0;
    if(L<=mid) re=get(ls[k],l,mid,L,R);
    if(R>mid) re+=get(rs[k],mid+1,r,L,R);
    return re;
}

inline void ask(int x,int y,int bs)
{
    int sum=0,num=0;
    for(;top[x]!=top[y];x=f[top[x]]){
        if(d[top[x]]<d[top[y]]) swap(x,y);
        if(bs>0) 
          num+=get(rt[bs],1,n,in[top[x]],in[x]);
        sum+=d[x]-d[top[x]]+1;
    }
    if(d[x]<d[y]) swap(x,y);
    if(bs>0) num+=get(rt[bs],1,n,in[y],in[x]);
    sum+=d[x]-d[y]+1;
    printf("%d %d\n",sum,num);
}

int main(){
    RI int i,j,op,x,y,cc;
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d",&f[i]);
        if(!f[i]) root=i;
        else lk(f[i],i);
    }
    dfs(root);df(root,root);
    scanf("%d",&m);
    for(i=1;i<=m;++i){
        scanf("%d",&op);
        rt[i]=rt[i-1];
        if(op==1){
            scanf("%d%d%d",&x,&y,&cc);
            ask(x,y,i-cc-1);
        }else{
            scanf("%d",&x);
            if(!vs[x]) {
                ins(rt[i-1],rt[i],1,n,in[x]);
                vs[x]=1;
            }
        }
    }
    return 0;
}

总结

考点:
二分图匹配 × 1
倍增 × 1
计算几何 × 1
简单数据结构/STL(平衡树/set 主席树) × 2
DP(树形) × 1

倍增再次出现?
主席树再次出现?
算几年年都有?
DP普遍毒瘤/不可做?

T1,2,3,5,6不能错

T4是一道非常好&锻炼思维的DP(考场上遇到最好弃掉,想不出来啊)

猜你喜欢

转载自blog.csdn.net/corsica6/article/details/82710750