VP NOI2023

发布时间 2023-08-31 13:34:15作者: by_chance

一个月前的事情捏,因为今天刚好在摸鱼就想起来写写。

Day 1

开题,先总的过一遍,好像比较传统。

T1 基本上是一眼题了,简单容斥一下就可以解决。很快开始写,写好过了小样例。但是这个时候还没有大样例。而我对这份代码并不太自信,感觉一车细节,所以开拍。
果然 WA 了。然后开始艰难地调试,毕竟代码确实是有点长。调了不知道多久终于过拍了,一看时间已经过去将近两小时了。感觉不太妙。

开 T2,感觉有点神秘,估计要放弃正解。先写个暴力过 1,2,再手推了 3~7 测试点,感觉不能做了。想着能过 \(k=0\) 就行,但是不会。先过。

T3 稍微分析一下看出一个性质,然后发现前 9 个点都是暴力,先写过去。再看看特殊性质,链不难,转化一下成为线段树优化 DP。写写写,拿小样例测了一下是过了,不知道写对没有。

再回去看看 T2,对 \(n=1\) 打表发现是双阶乘,这么神奇。

剩下的时间什么都不会。估分 100+50+52=202,但是没地方测。

Day 2

开题,居然有字符串。

T1 看 \(n\) 很小感觉是乱搞题。先写了一个暴力,然后开始想一些奇妙的东西,比如 \(n\) 次 dijk 之类的。然后突然发现这个 dijk 的结果可以继承,那好像就成了?复杂度上界是 \(O(2^nn^3)\),但应该多算了不少,能过的样子。写之,测一下极限数据,还比较稳,写个拍丢了。大概一个多小时的样子。

T2 字符串题,只好往我会的方向想。试试能不能用 SA,然后发现真的可以,用 SA 之后配一个 Manacher,加三次二维数点就解决了。但是 SA 我也不太会写啊,怎么办呢,硬写。凭着模糊的记忆,光写 SA 可能就写了四五十分钟,但还是写过去了。接下来就没什么好说的了,正常写数点,很快调过去了。

大概三个半小时了,看 T3,题目都没理解,但看数据范围肯定有 10 分暴力,说不定还能多搞点。但是不想写了。

这时又发现洛谷上有题和大样例了,把两天的代码都测一下,居然一点没挂。这样总分 100+50+52+100+100+(>=10)=(>=412),算上笔试过了队线。

这场 VP 有点码力爆发的意思,总代码量接近 15K(其中 Day 1 接近 10K),感觉状态还不错。

但过了有什么用呢,又去不了。NOIP 挂成什么都不知道,去了 NOI 估计也是挂分。以前还是练的太少太少了。只能说赶紧练,希望 24 年能够去见见世面。

下面是题解部分。

D1T1

给定方格平面上若干条线段,有水平线,竖直线和斜率为 \(1\) 的线,其中斜线不超过 \(5\) 条。求这些线经过的格子数目。

题解:答案可以表示成 水平线和竖直线的并 加上 斜线的并 减去 斜线与水平线,竖直线的交点数目。第一个值离散化后扫描线求,第二个值直接暴力,第三个值用 set 记一下就行。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int test,n,m,q,x[N<<2],y[N<<2],X,Y,cnt1,cnt2,cnt3;ll ans,cnt;
struct node{int x1,y1,x2,y2;};
bool operator <(const node&a,const node&b){return a.x1<b.x1;}
bool cmp(node a,node b){return a.x1-a.y1==b.x1-b.y1?a.x1<b.x1:a.x1-a.y1<b.x1-b.y1;}
vector<node> q1,q2,q3,q4,Q;
set<pair<int,int> > S;
ll sum[N<<4],tag[N<<4];
void modify(int p,int l,int r,int L,int R,int v){
    if(l>=L&&r<=R){
        if(v==1){++tag[p];sum[p]=y[r+1]-y[l];}
        else{--tag[p];if(!tag[p])sum[p]=(l==r?0:sum[p<<1]+sum[p<<1|1]);}
        return;
    }
    int mid=l+r>>1;
    if(L<=mid)modify(p<<1,l,mid,L,R,v);
    if(R>mid)modify(p<<1|1,mid+1,r,L,R,v);
    if(!tag[p])sum[p]=sum[p<<1]+sum[p<<1|1];
}
int main(){
    // freopen("color.in","r",stdin);
    // freopen("color.out","w",stdout);
    scanf("%d",&test);
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1,op;i<=q;i++){
        node tmp;
        scanf("%d%d%d%d%d",&op,&tmp.x1,&tmp.y1,&tmp.x2,&tmp.y2);
        if(op!=3)ans+=max(abs(tmp.x1-tmp.x2)+1,abs(tmp.y1-tmp.y2)+1);
        x[4*i-3]=tmp.x1;x[4*i-2]=tmp.x2;x[4*i-1]=tmp.x1+1;x[4*i]=tmp.x2+1;
        y[4*i-3]=tmp.y1;y[4*i-2]=tmp.y2;y[4*i-1]=tmp.y1+1;y[4*i]=tmp.y2+1;
        if(op==1){if(tmp.x1>tmp.x2)swap(tmp.x1,tmp.x2);q1.push_back(tmp);}
        if(op==2){if(tmp.y1>tmp.y2)swap(tmp.y1,tmp.y2);q2.push_back(tmp);}
        if(op==3)q4.push_back(tmp);
    }cnt=ans;
    sort(x+1,x+4*q+1);sort(y+1,y+4*q+1);
    X=unique(x+1,x+4*q+1)-x-1;Y=unique(y+1,y+4*q+1)-y-1;
    sort(q4.begin(),q4.end(),cmp);
    node xx;
    for(int i=0;i<q4.size();i++){
        if(i==0)xx=q4[i];
        if(i!=0&&q4[i].x1-q4[i].y1!=q4[i-1].x1-q4[i-1].y1)
            q3.push_back(xx),xx=q4[i];
        if(i!=0&&q4[i].x1-q4[i].y1==q4[i-1].x1-q4[i-1].y1){
            if(xx.x2<q4[i].x1)q3.push_back(xx),xx=q4[i];
            else xx.x2=max(q4[i].x2,xx.x2),xx.y2=max(q4[i].y2,xx.y2);
        }
        if(i==q4.size()-1)q3.push_back(xx);
    }
    for(node a:q3)ans+=a.x2-a.x1+1;
    for(node a:q3)for(node b:q1)
        if(a.y1<=b.y1&&a.y2>=b.y2&&
            b.y1-a.y1+a.x1>=b.x1&&b.y1-a.y1+a.x1<=b.x2)
            S.insert({b.y1-a.y1+a.x1,b.y1});
    for(node a:q3)for(node b:q2)
        if(a.x1<=b.x1&&a.x2>=b.x2&&
            b.x1-a.x1+a.y1>=b.y1&&b.x1-a.x1+a.y1<=b.y2)
            S.insert({b.x1,b.x1-a.x1+a.y1});
    for(node a:q1){
        a.x1=lower_bound(x+1,x+X+1,a.x1)-x;
        a.x2=lower_bound(x+1,x+X+1,a.x2+1)-x;
        a.y1=lower_bound(y+1,y+Y+1,a.y1)-y;
        a.y2=lower_bound(y+1,y+Y+1,a.y2)-y;
        Q.push_back({a.x1,a.y1,1,a.y2});
        Q.push_back({a.x2,a.y1,-1,a.y2});
    }
    for(node a:q2){
        a.x1=lower_bound(x+1,x+X+1,a.x1)-x;
        a.x2=lower_bound(x+1,x+X+1,a.x2+1)-x;
        a.y1=lower_bound(y+1,y+Y+1,a.y1)-y;
        a.y2=lower_bound(y+1,y+Y+1,a.y2)-y;
        Q.push_back({a.x1,a.y1,1,a.y2});
        Q.push_back({a.x2,a.y1,-1,a.y2});
    }
    sort(Q.begin(),Q.end());
    for(int i=1,p=0;i<=X;i++){
        while(p<Q.size()&&Q[p].x1==i)
            modify(1,1,Y,Q[p].y1,Q[p].y2,Q[p].x2),++p;
        cnt-=sum[1]*(x[i+1]-x[i]);
    }
    printf("%lld\n",ans-cnt-S.size());
    return 0;
}

D2T1

在一棵 \(n\) 层的满二叉树上加入若干条祖先指向后代的边,边有边权。树边由儿子指向父亲。对所有满足 \(u\) 能到达 \(v\) 的有序点对 \((u,v)\),求 \(u\)\(v\) 的最短路径长度和。

题解:注意到一个事实:如果 \(u\) 能到达不在 \(u\) 子树内的点 \(v\),那 \(u\)\(v\) 的最短路径就等于 \(u\) 沿着树边走到 \(LCA(u,v)\) 的路径长加上 \(LCA(u,v)\)\(v\) 的最短路径长。所以只要求出每个点到它子树内点的最短路径,再乘一些容易求的系数即可。

要求 \(u\) 到它所有后代的距离,可以从它的两个儿子继承而来。也就是说,儿子到其子树内点的最短距离加上连接它们的边的长度,可以作为初始值加入优先队列。然后跑 Dijkstra。

时间有点久了,真的忘了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5,P=998244353;
int n,m,a[N],rt,vis[N],dep[N];
ll dis[N],sum0,sum1,sum2,sum3,ans;
int head[N],nxt[N<<1],ver[N<<1],val[N<<1],tot;
void add(int u,int v,int w){
    ver[++tot]=v;val[tot]=w;
    nxt[tot]=head[u];head[u]=tot;
}
map<pair<int,int>,int> M;
priority_queue<pair<ll,int> > Q;
void init(int u){
    dis[u]+=a[rt];vis[u]=0;
    if(dis[u]<1e17)Q.push({-dis[u],u});
    if(u*2<(1<<n))init(2*u),init(2*u+1);
}
void dfs(int u){
    ++sum0;sum2=(sum2+(dep[u]-dep[rt]+P)%P)%P;
    if(dis[u]<1e17)sum1=(sum1+dis[u]%P)%P,++sum3;
    if(u*2<(1<<n))dfs(2*u),dfs(2*u+1);
}
void solve(int s){
    rt=s;init(s);dis[s]=0;Q.push({0,s});
    while(!Q.empty()){
        int u=Q.top().second;Q.pop();
        if(vis[u])continue;vis[u]=1;
        for(int i=head[u],v;i;i=nxt[i])
            if(dis[v=ver[i]]>dis[u]+val[i]&&v>rt){
                dis[v]=dis[u]+val[i];
                Q.push({-dis[v],v});
            }
    }
    sum0=sum3=1;sum2=sum1=0;dfs(2*s);
    ll sum4=sum0,sum5=sum1,sum6=sum2,sum7=sum3;
    sum0=sum3=1;sum2=sum1=0;dfs(2*s+1);
    ans=(ans+sum4*sum1%P+sum2*sum7%P
            +sum0*sum5%P+sum6*sum3%P)%P;
    if(s*2<(1<<n-1))solve(2*s),solve(2*s+1);
}
int main(){
    // freopen("trade.in","r",stdin);
    // freopen("trade.out","w",stdout);
    memset(dis,0x3f,sizeof(dis));
    scanf("%d%d",&n,&m);
    for(int i=2;i<(1<<n);i++){
        scanf("%d",a+i);
        add(i,i/2,a[i]);dep[i]=(dep[i/2]+a[i])%P;
    }
    for(int i=1,u,v,w;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        if(M.find({u,v})==M.end())M[{u,v}]=w;
        else M[{u,v}]=min(M[{u,v}],w);
    }
    for(auto x:M)add(x.first.first,x.first.second,x.second);
    solve(1);
    printf("%lld\n",ans);
    return 0;
}

D2T2

给定字符串 \(s[1,n]\)\(q\) 次询问,每次给定 \(i,r\),求满足 \(1\le l \le r\),且 \(s[i,i+l-1]\) 的字典序小于 \(s[i+l,i+2l-1]\) 的翻转的字典序。

题解:如何用 SA 刻画这个条件?不妨扩展一下,如果满足条件,则 \(s[i,n]\) 的字典序也小于 \(s[1,i+2l-1]\) 的翻转的字典序。那么就可以把原字符串翻转一遍接在后面,然后跑 SA,只要对应的字符串在后缀数组中排在前面就行。

但是有一点问题,就是可能 \(s[i,i+l-1]\) 等于 \(s[i+l,i+2l-1]\) 的翻转,但仍然排在前面。这时会发现构成了一个回文串,所以跑 Manacher。

列一下式子。\(rk[i]\) 表示后缀 \(s[i,n]+s[n,1]\) 的排名,\(rk[2n+1-i]\) 表示后缀 \(s[i,1]\) 的排名。\(a[i]\) 表示以 \(i\)\(i+1\) 的中心为缝隙的回文串的最大长度。则 \(l\) 满足条件,当且仅当 \(rk[i]\lt rk[2n+2-2l-i]\)\(a[i+l-1]\lt l\)

对第二个条件考虑反面。容易转成二维数点,剩下的推导是容易的。对第一个式子,我选择分奇偶各进行一次。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int test,T,n,q,a[N],ans[N];char s[N],t[N];
int c[N],x[N],y[N],sa[N],rk[N];
void SA(){
    int m=300;n*=2;
    for(int i=1;i<=n;i++)x[i]=t[i],c[x[i]]++;
    for(int i=1;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k*=2){
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);x[sa[1]]=num=1;
        for(int i=2;i<=n;i++){
            if(sa[i]<=n-k&&sa[i-1]<=n-k&&
                y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])
                x[sa[i]]=num;
            else x[sa[i]]=++num;
        }
        if(num==n)break;m=num;
    }
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    n/=2;
}
//sa[i]表示排名为i的后缀是s[sa[i]:n]
void Manacher(){
    int l=0,r=0;
    for(int i=1;i<n;i++){
        if(i>=r){
            int j=0;
            while(i-j>=1&&i+j+1<=n&&s[i-j]==s[i+j+1])++j;
            if(j>0){l=i-j+1;r=i+j;}a[i]=j;
        }
        else{
            int p=l+r-i-1;
            if(a[p]<r-i)a[i]=a[p];
            else{
                int j=r-i;
                while(i-j>=1&&i+j+1<=n&&s[i-j]==s[i+j+1])++j;
                r=i+j;l=i-j+1;a[i]=j;
            }
        }
    }
}
//a[i]表示以i和i+1间的缝隙为中心的最长回文串的长度的一半
struct BIT{
    int c[N];
    void init(){for(int i=1;i<=2*n;i++)c[i]=0;}
    void add(int x,int v){for(;x<=2*n;x+=x&-x)c[x]+=v;}
    int ask(int x){int res=0;for(;x;x-=x&-x)res+=c[x];return res;}
}B;
struct query{int l,r,id;}f[N];
int id[N],cnt;
bool cmp1(int i,int j){return i-a[i]<j-a[j];}
bool cst1(query a,query b){return a.l<b.l;}
bool cst2(query a,query b){return rk[a.l]>rk[b.l];}
void init(){
    memset(a,0,sizeof(a));
    memset(sa,0,sizeof(sa));
    memset(ans,0,sizeof(ans));
    memset(c,0,sizeof(c));
    memset(x,0,sizeof(x));
    memset(y,0,sizeof(y));
}
int main(){
    // freopen("string.in","r",stdin);
    // freopen("string.out","w",stdout);
    scanf("%d%d",&test,&T);
    while(T--){
        init();
        scanf("%d%d",&n,&q);
        scanf("%s",s+1);
        for(int i=1;i<=n;i++)t[i]=t[2*n+1-i]=s[i];
        Manacher();SA();
        for(int i=1;i<=q;i++)
            scanf("%d%d",&f[i].l,&f[i].r),f[i].id=i;
        B.init();cnt=0;
        for(int i=1;i<n;i++)
            if(a[i]&&rk[i+1-a[i]]<rk[2*n+1-i-a[i]])id[++cnt]=i;
        sort(id+1,id+cnt+1,cmp1);sort(f+1,f+q+1,cst1);
        for(int i=1,p=1;i<=q;i++){
            while(p<=cnt&&id[p]-a[id[p]]+1<=f[i].l)
                {B.add(id[p],1);++p;}
            ans[f[i].id]-=B.ask(f[i].l+f[i].r-1)-B.ask(f[i].l-1);
        }
        B.init();cnt=0;
        for(int i=2*n;i>=1;i--)
            if(sa[i]&1)id[++cnt]=i;
        sort(f+1,f+q+1,cst2);
        for(int i=1,p=1;i<=q;i++)if(f[i].l&1){
            while(p<=cnt&&id[p]>rk[f[i].l]){B.add(sa[id[p]],1);++p;}
            ans[f[i].id]+=B.ask(2*n-f[i].l)-B.ask(2*n+1-2*f[i].r-f[i].l);
        }
        B.init();cnt=0;
        for(int i=2*n;i>=1;i--)
            if(!(sa[i]&1))id[++cnt]=i;
        for(int i=1,p=1;i<=q;i++)if(!(f[i].l&1)){
            while(p<=cnt&&id[p]>rk[f[i].l]){B.add(sa[id[p]],1);++p;}
            ans[f[i].id]+=B.ask(2*n-f[i].l)-B.ask(2*n+1-2*f[i].r-f[i].l);
        }
        for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
    }
    return 0;
}