CSP-S 2021 游记 & 总结

游记? 游寄!

​ 考前几天一直在打模拟赛,把把被吊着打。

​ 考试前在场外口胡:今年有字符串;上午普及没\(DP\),下午也没有;CSP不会考网络流;(flag)

​ 考试前在考场外面等了很长时间,好像我们考场进的非常晚...

​ 考试键盘非常软!差评! 考试电脑非常卡!差评!\(\mathrm{14:31}\) 才给解压密码! 差评!

T1.廊桥分配

​ 考试的时候没多想,看了一会觉得 \(T1\) 不会这么难吧,于是猜了一下有单峰性写了个三分,于是就寄了。

​ 正解的话,首先我们知道,对于任何一架飞机,他是否能停靠在廊桥与他后面到达机场的飞机无关。

​ 所以有事实:若可用廊桥数为 \(k\) 时 某架飞机可以停在廊桥,那么当廊桥数大于 \(k\) 时该飞机也可以停在廊桥。

​ 那么我们先假设有无限座廊桥,然后对每架飞机,我们要求他如果可以停靠在廊桥,就停靠在编号最小的可以停靠的廊桥。我们求出对于每架飞机他停在了第 \(f_i\) 座廊桥上。那么第 \(i\) 架飞机能停靠廊桥当且仅当廊桥数量大于等于 \(f_i\)

​ 可以用优先队列模拟得出 \(f_i\) 。然后枚举怎么分配机场,取最大值即可。

​ 具体实现时,先对飞机按照时间排序,然后离散化。把 \(f_i\) 求出来之后可以树状数组,不过已经离散化过了,所以可以前缀和。

​ 时间复杂度:\(\mathcal{O}(n\log n)\)

​ Code:

int n,m1,m2;
struct node{
    int a,b;
    bool operator <(const node &p) const{
        return a<p.a;
    }
};
node g[2][DMAX];
int b[DMAX<<1],cnt=0;
int f[2][DMAX];
int tim[DMAX<<1];
int tag[DMAX<<1];
int pre[2][DMAX<<1];
int maxt1,maxt2;
priority_queue<int,vector<int>,greater<int> > q;
void solve(){
    for(int i=1;i<=m1;i++){
        q.push(i);
        tim[g[0][i].a]=i;
        tim[g[0][i].b]=i;
    }
    for(int i=1;i<=maxt1;i++){
        if(tim[i]==0){
            continue;
        }
        int now=tim[i];
        if(!tag[now]){
            int u=q.top();
            q.pop();
            tag[now]=u;
            f[0][now]=u;
        }
        else{
            q.push(tag[now]);
            tag[now]=0;
        }
    }
    mem(tim,0);
    while(!q.empty()){
        q.pop();
    }

    for(int i=1;i<=m2;i++){
        q.push(i);
        tim[g[1][i].a]=i;
        tim[g[1][i].b]=i;
    }
    for(int i=1;i<=maxt2;i++){
        if(tim[i]==0){
            continue;
        }
        int now=tim[i];
        if(!tag[now]){
            int u=q.top();
            q.pop();
            tag[now]=u;
            f[1][now]=u;
        }
        else{
            q.push(tag[now]);
            tag[now]=0;
        }
    }
}
int main(){ 
    read(n),read(m1),read(m2);
    for(int i=1;i<=m1;i++){
        read(g[0][i].a),read(g[0][i].b);
        b[++cnt]=g[0][i].a,b[++cnt]=g[0][i].b;
    }
    if(n>=m1+m2){
        printf("%d\n",m1+m2);
        return 0;
    }
    sort(b+1,b+cnt+1);
    int len=unique(b+1,b+cnt+1)-b-1;
    maxt1=0;
    for(int i=1;i<=m1;i++){
        g[0][i].a=lower_bound(b+1,b+len+1,g[0][i].a)-b;
        g[0][i].b=lower_bound(b+1,b+len+1,g[0][i].b)-b;
        maxt1=max(maxt1,g[0][i].b);
    }
    cnt=0;
    for(int i=1;i<=m2;i++){
        read(g[1][i].a),read(g[1][i].b);
        b[++cnt]=g[1][i].a,b[++cnt]=g[1][i].b;
    }
    sort(b+1,b+cnt+1);
    len=unique(b+1,b+cnt+1)-b-1;
    maxt2=0;
    for(int i=1;i<=m2;i++){
        g[1][i].a=lower_bound(b+1,b+len+1,g[1][i].a)-b;
        g[1][i].b=lower_bound(b+1,b+len+1,g[1][i].b)-b;
        maxt2=max(maxt2,g[1][i].b);
    }
    sort(g[1]+1,g[1]+m2);
    sort(g[0]+1,g[0]+m1);
    solve();
    for(int i=1;i<=m1;i++){
        pre[0][f[0][i]]++;
    }
    for(int i=1;i<=m2;i++){
        pre[1][f[1][i]]++;
    }
    for(int i=1;i<=maxt1;i++){
        pre[0][i]+=pre[0][i-1];
    }
    for(int i=1;i<=maxt2;i++){
        pre[1][i]+=pre[1][i-1];
    }
    int ans=0;
    for(int i=0;i<=n;i++){
        int red=n-i;
        ans=max(ans,pre[0][i]+pre[1][red]);
    }
    printf("%d\n",ans);
    return 0;
}
T2.括号序列

​ 怎么又是括号序列,考场上直接跳了。好像大家都做出来了,蒟蒻太菜了/kk。

​ 正解是区间DP,可以不动脑子的设计状态:令 \(f(i,j),g(i,j),h(i,j),p(i,j),q(i,j)\) 分别表示区间 \([i,j]\) 中替换 \(?\) 得到 \((A'), A,SA,AS,S\) 的方案数,\((A')\) 表示最外层为一对匹配括号的超级括号序列。

​ 初始化:所有长度为 \(1\) 的区间只有是 \(*\) 或者 \(?\) 时,\(q\) 为 $ 1$ 。其余均为 $0 $。

​ 考虑如何转移。

​ 首先是 \(q\)\(q(l,r)=1\) 当且仅当 \(s[l..r]\) 全是 \(?\) 或者 \(*\)

​ 接下来考虑 \(h,p\)\(h,p\) 转移注意一下转移边界。

​ 接着是 \(f\)\(f\) 最外层有一对配对括号,所以 \(f\) 只能为 \((A),(SA),(AS),(),(S)\) 这五种情况。

​ 最后是 \(g\)\(AB\)\(ASB\) 其实都可以看做后者(对于前者让 \(|S|=0\) 就可以了)。那么 \(g\) 所表示的最终形式一定形如 (...)***(...)***(...) 。我们转移时枚举第一个右括号的位置,因为我们已经计算过 \(f,q,h\) 了,所以转移就是把这三个拼起来。最后再把 \(f(l,r)\) 加上。

\(h(l,r)=\sum\limits_{i=l}^{r-1}q(l,i)*g(i+1,r)\)

\(p(l,r)=\sum\limits_{i=l+1}^{r-1}g(l,i)*q(i+1,r)\)

\(f(l,r)=g(l+1,r-1)+h(l+1,r-1)+p(l+1,r-1)+q(l+1,r-1)+[r-l+1=2]*[s[l..r]=()]\)

\(g(l,r)=f(l,r)+\sum\limits_{i=l+1}^{r-1}f(l,i)*(h(i+1,r)+g(i+1,r))\)

​ 时间复杂度:\(\mathcal{O}(n^3)\)

​ Code:

int n,k;
int f[DMAX][DMAX],g[DMAX][DMAX]; 
int h[DMAX][DMAX],p[DMAX][DMAX];
int q[DMAX][DMAX];
char s[DMAX];
// f:(A')  g:A   h:SA  p:AS q:S
int main(){
    read(n),read(k);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++){
        if(s[i]=='*' || s[i]=='?'){
            q[i][i]=1;
        }
    }
    for(int len=2;len<=n;len++){
        for(int l=1;l<=n-len+1;l++){
            int r=l+len-1;
            if(r-l+1>k){
                q[l][r]=0;
            }
            else{
                bool flag=0;
                for(int i=l;i<=r;i++){
                    if(s[i]!='*' && s[i]!='?'){
                        q[l][r]=0;
                        flag=1;
                        break;
                    }
                }
                if(!flag){
                    q[l][r]=1;
                }
            }
            if(r-l+1==2){
                if((s[l]=='(' && s[r]==')') || (s[l]=='(' && s[r]=='?') || (s[l]=='?' && s[r]==')') || (s[l]=='?' && s[r]=='?')){
                    f[l][r]=(0ll+f[l][r]+1)%MOD;
                }
            }
            if((s[l]=='(' && s[r]==')') || (s[l]=='(' && s[r]=='?') || (s[l]=='?' && s[r]==')') || (s[l]=='?' && s[r]=='?')){
                if(q[l+1][r-1]){
                    f[l][r]=(f[l][r]+1)%MOD;
                }
                f[l][r]=(0ll+f[l][r]+g[l+1][r-1])%MOD;
                f[l][r]=(0ll+f[l][r]+h[l+1][r-1])%MOD;
                f[l][r]=(0ll+f[l][r]+p[l+1][r-1])%MOD;
            }
            for(int i=l+1;i<=r-1;i++){
                int now=(0ll+h[i+1][r]+g[i+1][r])%MOD;
                g[l][r]=(0ll+g[l][r]+1ll*f[l][i]*now%MOD)%MOD;
            }
            for(int i=l;i<=r-1;i++){
                h[l][r]=(0ll+h[l][r]+1ll*q[l][i]*g[i+1][r]%MOD)%MOD;
            }

            for(int i=l+1;i<=r-1;i++){
                p[l][r]=(0ll+p[l][r]+1ll*g[l][i]*q[i+1][r]%MOD)%MOD;
            }
            g[l][r]=(0ll+g[l][r]+f[l][r])%MOD;
        }
    }
    printf("%d\n",g[1][n]);
    return 0;
}
T3.回文

​ 考场上只有暴力,没看出来结论,好像大眼看样例就能看出来,我眼太小(

​ 直接给结论:对于前 \(n\) 个删除的数,这 \(n\) 个数组成的排列 \(p\) 的任意前缀,都是后 \(n\) 个数的一个区间。(充要条件

​ 结论的话手玩一下应该就能发现。不过既然是结论还是考虑证明一下吧。前 \(n\) 次选出来的数形成的序列也就是 \(b\) 的前 \(n\) 个数,而这样它的任意前缀即为 \(b\) 的任意前缀,同时由于 \(b\) 是回文,所以也是任意后缀。那这样一定是连续被选择出来添加到末尾的,即一定是一段区间。

​ 那么我们现在只需考虑第一个数取哪个,分类讨论即可。选数的时候贪心就可以了,优先选择从左边取,选的同时不断地拓展区间。选完 \(n\) 个数,剩下 \(n\) 个稍微操作一下就行了。

时间复杂度:\(\mathcal{O}(Tn)\)

​ Code:

int T;
int n;
int a[DMAX<<1];
int pos[DMAX];
int link[DMAX<<1];
string s;
int st[DMAX],top;
bool tak[DMAX<<1];
bool hve=0;
void solve1(){
    int l,r;
    l=r=link[1];
    int dl=2,dr=2*n;
    mem(tak,0);
    tak[a[1]]=1;
    int hve=1;
    string t="L";
    top=0;
    st[++top]=a[1];
    bool find=1;
    while(1){
        if(hve==n){
            break;
        }
        if(l-link[dl]==1 && !tak[a[dl]]){
            t+='L';
            st[++top]=a[dl];
            tak[a[dl]]=1;
            dl++,l--,hve++;
            continue;
        }
        if(link[dl]-r==1  && !tak[a[dl]]){
            t+='L';
            st[++top]=a[dl];
            tak[a[dl]]=1;
            dl++,r++,hve++;
            continue;
        }
        if(link[dr]-r==1 && !tak[a[dr]]){
            t+='R';
            st[++top]=a[dr];
            tak[a[dr]]=1;
            dr--,r++,hve++;
            continue;
        }
        if(l-link[dr]==1 && !tak[a[dr]]){
            t+='R';
            st[++top]=a[dr];
            tak[a[dr]]=1;
            dr--,l--,hve++;
            continue;
        }
        find=0;
        break;
    }
    if(!find){
        return ;
    }
    while(dl<=dr){
        if(a[dl]==st[top]){
            t+='L';
            dl++,top--;
            continue;
        }
        if(a[dr]==st[top]){
            t+='R';
            dr--,top--;
            continue;
        }
        find=0;
        break;
    }
    if(!find){
        return ;
    }
    s=t;
}
void solve2(){
    int l,r;
    l=r=link[2*n];
    int dl=1,dr=2*n-1;
    int hve=1;
    string t="R";
    top=0;
    st[++top]=a[2*n];
    mem(tak,0);
    tak[a[2*n]]=1;
    bool find=1;
    while(1){
        if(hve==n){
            break;
        }
        if(l-link[dl]==1 && !tak[a[dl]]){
            t+='L';
            st[++top]=a[dl];
            tak[a[dl]]=1;
            dl++,l--,hve++;
            continue;
        }
        if(link[dl]-r==1  && !tak[a[dl]]){
            t+='L';
            st[++top]=a[dl];
            tak[a[dl]]=1;
            dl++,r++,hve++;
            continue;
        }
        if(link[dr]-r==1 && !tak[a[dr]]){
            t+='R';
            st[++top]=a[dr];
            tak[a[dr]]=1;
            dr--,r++,hve++;
            continue;
        }
        if(l-link[dr]==1 && !tak[a[dr]]){
            t+='R';
            st[++top]=a[dr];
            tak[a[dr]]=1;
            dr--,l--,hve++;
            continue;
        }
        find=0;
        break;
    }
    if(!find){
        return ;
    }
    while(dl<=dr){
        if(a[dl]==st[top]){
            t+='L';
            dl++,top--;
            continue;
        }
        if(a[dr]==st[top]){
            t+='R';
            dr--,top--;
            continue;
        }
        find=0;
        break;
    }
    if(!find){
        return ;
    }
    if(s=="?"){
        s=t;
    }
}
int main(){
    freopen("palin.in","r",stdin);
    freopen("palin.out","w",stdout);
    read(T);
    while(T--){
        read(n);
        mem(link,0);
        mem(pos,0);
        for(int i=1;i<=2*n;i++){
            read(a[i]);
            if(!pos[a[i]]){
                pos[a[i]]=i;
            }
            else{
                link[i]=pos[a[i]];
                link[pos[a[i]]]=i;
            }
        }
        hve=0;
        s="?";
        solve1();
        if(s!="?"){
            cout<<s<<endl;
            continue;
        }
        solve2();
        if(s=="?"){
            printf("-1\n");
        }
        else{
            cout<<s<<endl;
        }
    }
    return 0;
}

T4.交通规划

​ 考场上想到了 \(k=2\) 的最小割,也做过狼抓兔子。但是我觉得CSP不会考网络流,就没写。血亏!(现在还只会暴力和 \(k=2\) 的做法)

​ 对于 \(k=2\) 的情况,如果两个附加点颜色相同,那么答案为 \(0\) 。如果不同,那就是经典的平面图最小割问题。这个可以转化为对偶图最短路。建图可以如下建图:

(绿叉表示白色点,棕叉表示黑色点),我们求的就是红色勾到蓝色勾的最短路。

时间复杂度:\(\mathcal{O}(Tnm\log nm)\)

Code:

struct edge{
    int to,nxt;
    ll len;
    int from;
};
edge g[DMAX<<1];
int head[DMAX],cnt=0;
void addedge(int u,int v,ll w){
    g[++cnt].to=v;
    g[cnt].from=u;
    g[cnt].nxt=head[u];
    g[cnt].len=w;
    head[u]=cnt;
}
int n,m,T,q;
int lef[505][505],up[505][505];
struct extra{
    int x,y;
    int col;
    int lab;
    int val;
    int sx,sy;
    int lx,ly;
};
extra a[55];
int col[5][5];
ll ans;
void DFS(int x,int y,ll now){
    if(now>ans){
        return ;
    }
    if(x==n+1 && y==1){
        ll cost=now;
        for(int i=1;i<=q;i++){
            if(a[i].col!=col[a[i].lx][a[i].ly]){
                cost+=a[i].val;
            }
        }
        ans=min(ans,cost);
        return ;
    }
    col[x][y]=0;
    ll res=now;
    if(col[x-1][y]==1){
        res+=up[x][y];
    }
    if(col[x][y-1]==1){
        res+=lef[x][y];
    }
    if(y+1>m){
        DFS(x+1,1,res);
    }
    else{
        DFS(x,y+1,res);
    }
    col[x][y]=1;
    res=now;
    if(col[x-1][y]==0){
        res+=up[x][y];
    }
    if(col[x][y-1]==0){
        res+=lef[x][y];
    }
    if(y+1>m){
        DFS(x+1,1,res);
    }
    else{
        DFS(x,y+1,res);
    }
    col[x][y]=0;
}
int Position(int x,int y){
    return (x-1)*(m+1)+y;
}
ll dis[DMAX];
bool vst[DMAX];
int linx[DMAX],liny[DMAX];
int redf[DMAX];
queue<int> dq[2];
void Dij(){
    for(int i=0;i<=260100;i++){
        dis[i]=1e18;
        vst[i]=0;
    }
    priority_queue<pair<ll,int> > qd;
    int kpo=0;
    int st=a[1].lab,ed=a[2].lab;
    if(st>ed){
        swap(st,ed);
    }
    while(!dq[0].empty()){
        dq[0].pop();
    }
    st++;
    while(1){
        if(dis[redf[st]]==0){
            break;
        }
        dq[kpo].push(redf[st]);
        if(kpo==0){
            qd.push(mp(0,redf[st]));
            dis[redf[st]]=0;
        }
        if(st==ed){
            kpo^=1;
        }
        st++;
        if(st==2*n+2*m+1){
            st=1;
        }
    }
    while(!qd.empty()){
        int u=qd.top().second;
        qd.pop();
        if(vst[u]){
            continue;
        }
        vst[u]=1;
        for(int i=head[u];i;i=g[i].nxt){
            int v=g[i].to;
            if(dis[v]>dis[u]+g[i].len){
                dis[v]=dis[u]+g[i].len;
                if(!vst[v]){
                    qd.push(mp(-dis[v],v));
                }
            }
        }
    }
    return ;
}
int main(){
    read(n),read(m),read(T);
    for(int i=1;i<=n-1;i++){
        for(int j=1;j<=m;j++){
            read(up[i+1][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<m;j++){
            read(lef[i][j+1]);
        }
    }
    int pos;
    while(T--){
        read(q);
        ans=1e18;
        for(pos=1;pos<=2*m+2*n;pos++){
            int i=0;
            if(pos>=1 && pos<=m){
                a[i].x=0,a[i].y=pos;
                a[i].lx=1,a[i].ly=pos;
                a[i].sx=1,a[i].sy=pos;
                linx[pos]=a[i].sx,liny[pos]=a[i].sy;
                redf[pos]=Position(linx[pos],liny[pos]);
            }
            if(pos>=m+1 && pos<=n+m){
                a[i].x=pos-m,a[i].y=m+1;
                a[i].lx=pos-m,a[i].ly=m;
                a[i].sx=pos-m,a[i].sy=m+1;
                linx[pos]=a[i].sx,liny[pos]=a[i].sy;
                redf[pos]=Position(linx[pos],liny[pos]);
            }
            if(pos>=m+n+1 && pos<=2*m+n){
                a[i].x=n+1,a[i].y=m-(pos-m-n)+1;
                a[i].lx=n,a[i].ly=a[i].y;
                a[i].sx=n+1,a[i].sy=a[i].y;
                linx[pos]=a[i].sx,liny[pos]=a[i].sy+1;
                redf[pos]=Position(linx[pos],liny[pos]);
            }
            if(pos>=2*m+n+1 && pos<=2*m+2*n){
                a[i].x=n-(pos-2*m-n)+1,a[i].y=0;
                a[i].lx=a[i].x,a[i].ly=1;
                a[i].sx=a[i].lx,a[i].sy=a[i].y;
                linx[pos]=a[i].sx+1,liny[pos]=a[i].sy+1;
                redf[pos]=Position(linx[pos],liny[pos]);
            }
        }
        for(int i=1;i<=q;i++){
            read(a[i].val),read(pos);
            read(a[i].col);
            a[i].lab=pos;
            if(pos>=1 && pos<=m){
                a[i].x=0,a[i].y=pos;
                a[i].lx=1,a[i].ly=pos;
                a[i].sx=1,a[i].sy=pos;
            }
            if(pos>=m+1 && pos<=n+m){
                a[i].x=pos-m,a[i].y=m+1;
                a[i].lx=pos-m,a[i].ly=m;
                a[i].sx=pos-m,a[i].sy=m+1;
            }
            if(pos>=m+n+1 && pos<=2*m+n){
                a[i].x=n+1,a[i].y=m-(pos-m-n)+1;
                a[i].lx=n,a[i].ly=a[i].y;
                a[i].sx=n+1,a[i].sy=a[i].y;
            }
            if(pos>=2*m+n+1 && pos<=2*m+2*n){
                a[i].x=n-(pos-2*m-n)+1,a[i].y=0;
                a[i].lx=a[i].x,a[i].ly=1;
                a[i].sx=a[i].lx,a[i].sy=a[i].y;
            }
        }
        if(n<=5 && m<=5){
            for(int i=0;i<=n+1;i++){
                for(int j=0;j<=m+1;j++){
                    col[i][j]=-1;
                }
            }
            DFS(1,1,0);
            printf("%lld\n",ans);
            continue;
        }
        mem(head,0);
        cnt=0;
        if(q==1){
            printf("0\n");
            continue;
        }
        if(q<=2){
            if(a[1].col==a[2].col){
                printf("0\n");
                continue;
            }
            for(int i=1;i<=n-1;i++){
                for(int j=1;j<=m;j++){
                    addedge(Position(i+1,j),Position(i+1,j+1),up[i+1][j]);
                    addedge(Position(i+1,j+1),Position(i+1,j),up[i+1][j]);
                }
            }
            for(int i=1;i<=n;i++){
                for(int j=1;j<m;j++){
                    addedge(Position(i,j+1),Position(i+1,j+1),lef[i][j+1]);
                    addedge(Position(i+1,j+1),Position(i,j+1),lef[i][j+1]);
                }
            }
            for(int i=1;i<=q;i++){
                int x=a[i].sx,y=a[i].sy;
                if(x==1 && y!=m+1 && y!=0){
                    addedge(Position(x,y),Position(x,y+1),a[i].val);
                    addedge(Position(x,y+1),Position(x,y),a[i].val);
                    continue;
                }
                if(y==m+1){
                    addedge(Position(x,y),Position(x+1,y),a[i].val);
                    addedge(Position(x+1,y),Position(x,y),a[i].val);
                    continue;
                }
                if(x==n+1){
                    addedge(Position(x,y),Position(x,y+1),a[i].val);
                    addedge(Position(x,y+1),Position(x,y),a[i].val);
                    continue;
                }
                if(y==0){
                    addedge(Position(x,y+1),Position(x+1,y+1),a[i].val);
                    addedge(Position(x+1,y+1),Position(x,y+1),a[i].val);
                    continue;
                }
            }
            Dij();
            ll red=INF;
            while(!dq[1].empty()){
                int u=dq[1].front();
                dq[1].pop();
                red=min(red,dis[u]);
            }
            ans=min(ans,red);
            printf("%lld\n",ans);
        }
    }
    return 0;
}
总结

​ 好像我们省CSP有分就能进NOIP,所以只要不爆零就行。于是就随便考

​ 感觉今年比去年难度更大?NOIP是不是难度更大啊?退役了。

​ 考场上策略还有点问题,模拟赛要认真打了。

posted @ 2021-10-27 10:43  Vitheon  阅读(158)  评论(0编辑  收藏  举报