noip模拟50[丧]

noip模拟50 solutions

队奶奶暴力打满(即使是暴力也比我的暴力高明),拿到rank1,220pts,而我只有可怜的60pts

好丧啊,连续两次都在中游,考场上脑子就像浆糊一样,啥也想不到

还有一个就是懒,暴力懒得打,算算复杂度,知道不可能拿到更多的分就不打有优化的暴力

可是有时候出题人就很SB,数据做的非常水,导致我想到的可以卡掉优化暴力的数据是没有的

然后稍微优化一下就可以AC,我却懒得做了。

总之我出现了一个新的问题,太懒了,以至于该拿到的分根本拿不到

所以以后要注意打暴力。。。。。

T1 第零题

仍然是暴力出奇迹的一道玄学题,不是做法玄学,而是数据玄学

我一开始认为是树剖+神奇线段树,然后想了半天如何处理边界问题

开场想暴力的时候想到了要处理向上的第一个复活的地方,但是我没有想到倍增,就差一点点

我知道一定会有一个结论来方便我处理从上向下的链

也就是题解里的神秘的观察:从上到下和从下到上是一样的

我确实是想到了,而且也尝试证明了,没有证出来,所以没有敢用

然后我想要一个一个复活点的跳,但是想了想没有啥用,因为特殊构造一定可以卡掉

最后直接弃掉了这个题,40pts就走人了。

刚才的神秘观察:对于一条链,如果体力都是满的从两边开始走,那么复活的次数是一样的

这个在沈队爷的提醒之下,应用了调整法

我们将从s到t的路径记录下来成为一个序列,这样我们按照题目说的走一遍。

我们可以对于每一个复活区间删掉左端点,那么区间就会一点一点的向后平移

这样复活的次数是一定的,所以我们就可以以lca为中心,两侧的向两侧移动,让中间空出来

这样我们就可以直接从s和t开始向上倍增,最后看看剩下的那一段是否大于k,大于的话就+1

AC_code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
const int N=2e5+5;
int n,k,q;
int to[N*2],nxt[N*2],c[N*2],head[N],rp;
void add_edg(int x,int y,int z){
    to[++rp]=y;
    c[rp]=z;
    nxt[rp]=head[x];
    head[x]=rp;
}
int siz[N],son[N],dep[N],fa[N],top[N];
int dfn[N],idf[N],cnt;
int sum[N],val[N];
void dfs_fi(int x){
    siz[x]=1;son[x]=0;
    for(re i=head[x];i;i=nxt[i]){
        int y=to[i];
        if(y==fa[x])continue;
        dep[y]=dep[x]+1;
        fa[y]=x;
        val[y]=c[i];
        dfs_fi(y);
        siz[x]+=siz[y];
        if(!son[x]||siz[y]>siz[son[x]])son[x]=y;
    }
}
int st[N][21],ji[N],cj;
int find(int x){
    int l=0,r=x-1,mid;
    while(l<r){
        mid=l+r+1>>1;
        if(sum[ji[x]]-sum[ji[mid]]<k)r=mid-1;
        else l=mid;
    }
    return ji[l];
}
void dfs_se(int x,int f){
    top[x]=f;dfn[x]=++cnt;idf[cnt]=x;
    cj++;ji[cj]=x;
    sum[x]=sum[ji[cj-1]]+val[x];
    st[x][0]=find(cj);
    for(re i=1;i<=20;i++)st[x][i]=st[st[x][i-1]][i-1];
    if(son[x])dfs_se(son[x],f);
    for(re i=head[x];i;i=nxt[i]){
        int y=to[i];
        if(y==fa[x]||y==son[x])continue;
        dfs_se(y,y);
    }
    cj--;
}
int LCA(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}
signed main(){
    scanf("%lld%lld",&n,&k);
    for(re i=1;i<n;i++){
        int x,y,z;scanf("%lld%lld%lld",&x,&y,&z);
        add_edg(x,y,z);add_edg(y,x,z);
    }
    dep[0]=0;dep[1]=1;
    dfs_fi(1);dfs_se(1,1);
    scanf("%lld",&q);
    while(q--){
        int x,y,ans=0;
        scanf("%lld%lld",&x,&y);
        int lca=LCA(x,y);
        //cout<<lca<<endl;
        for(re i=20;i>=0;i--){
            if(dep[st[x][i]]>=dep[lca])x=st[x][i],ans+=pow(2,i);
            if(dep[st[y][i]]>=dep[lca])y=st[y][i],ans+=pow(2,i);
        }
        if(sum[x]-sum[lca]+sum[y]-sum[lca]>=k)ans++;
        printf("%lld\n",ans);
    }
}

T2 第负一题

这个题是真的难,我考场上一直在枚举左端点,然后找到不同起点的dp数组的关系

甚至还想用矩阵快速幂直接找答案,后来发现好像这个矩阵并不符合乘法分配律

所以我又弃掉了这个题,然而此时已经过去了两个半小时。。。。

正解是分治,就是通过分治跨越当前分治中心的贡献

对题解进行阐述,官方的确实说的不是人话。。。。

设f[i][1/0]表示,在分治中心(就是mid)的左侧走到的i节点的最大值(就是i~mid这个区间)0表示不选mid,1就是选mid

设g[i][1/0]表示,右侧走到i节点的最大值(就是mid+1~r这个区间),0/1意义同上

这时候就明朗了,我们的答案就是要求跨过中心的所有区间的和。

也就是左右两侧任选节点求和,答案就是

\[\displaystyle\sum^{mid}_{i=l}\sum^{r}_{j=mid+1}max(f[i][0]+g[j][1],f[i][1]+g[j][0],f[i][0]+g[j][0]) \]

你会发现后面的式子好麻烦,所以我们直接换掉它,

\(cf[i]=max(f[i][1]-f[i][0],0),cg[i]=max(g[i][1]-g[i][0],0)\)

这样的话,上面的就可以化简成

\[\displaystyle\sum^{mid}_{i=l}\sum^{r}_{j=mid+1}(f[i][0]+g[j][0]+max(cf[i],cg[j])) \]

那就好说了,拆开来看,前面的两项可以直接\(\mathcal{O(n)}\)

后面的max可以直接对cf,cg进行排序,直接单调指针扫一遍就好了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
const int N=2e5+5;
const int mod=998244353;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,a[N],ans;
int f[N][2],g[N][2];
int cf[N],cg[N];
int dp[N][2];
void get(int l,int r){
    if(l==r){
        ans=(ans+a[l])%mod;
        //cout<<l<<" "<<ans<<endl;
        return ;
    }
    int mid=l+r>>1;
    get(l,mid);get(mid+1,r);
    for(re i=l;i<=r;i++)dp[i][0]=dp[i][1]=0;
    dp[mid][0]=0;dp[mid][1]=-inf;f[mid][0]=0;
    for(re i=mid-1;i>=l;i--)dp[i][0]=max(dp[i+1][0],dp[i+1][1]),dp[i][1]=dp[i+1][0]+a[i],f[i][0]=max(dp[i][0],dp[i][1]);
    dp[mid+1][0]=0;dp[mid][1]=-inf;g[mid+1][0]=0;
    for(re i=mid+2;i<=r;i++)dp[i][0]=max(dp[i-1][0],dp[i-1][1]),dp[i][1]=dp[i-1][0]+a[i],g[i][0]=max(dp[i][0],dp[i][1]);
    for(re i=l;i<=r;i++)dp[i][0]=dp[i][1]=0;
    dp[mid][0]=-inf;dp[mid][1]=a[mid];f[mid][1]=a[mid];
    for(re i=mid-1;i>=l;i--)dp[i][0]=max(dp[i+1][0],dp[i+1][1]),dp[i][1]=dp[i+1][0]+a[i],f[i][1]=max(dp[i][0],dp[i][1]);
    dp[mid+1][0]=-inf;dp[mid+1][1]=a[mid+1];g[mid+1][1]=a[mid+1];
    for(re i=mid+2;i<=r;i++)dp[i][0]=max(dp[i-1][0],dp[i-1][1]),dp[i][1]=dp[i-1][0]+a[i],g[i][1]=max(dp[i][0],dp[i][1]);
    for(re i=l;i<=mid;i++)cf[i]=max(f[i][1]-f[i][0],0ll),ans=(ans+f[i][0]*(r-mid))%mod;
    for(re i=mid+1;i<=r;i++)cg[i]=max(g[i][1]-g[i][0],0ll),ans=(ans+g[i][0]*(mid-l+1))%mod;
    sort(cf+l,cf+mid+1);sort(cg+mid+1,cg+r+1);
    int il=l,ir=mid;
    while(il<=mid){
        while(cg[ir+1]<=cf[il]&&ir<r)ir++,ans=(ans+cg[ir]*(il-l))%mod;
        ans=(ans+cf[il]*(ir-mid))%mod;il++;
    }
    while(ir<r){ir++;ans=(ans+cg[ir]*(mid-l+1))%mod;}
    //cout<<ans<<endl;
    return ;
}
signed main(){
    scanf("%lld",&n);
    for(re i=1;i<=n;i++)scanf("%lld",&a[i]);
    get(1,n);
    printf("%lld",ans);
}

T3 第负二题

所以这个题我是\(\mathcal{O(n^2)}\)水过去的

就直接枚举删第几次,然后去枚举每一行怎么删

垃圾暴力90pts
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
const int N=5e6+5;
const int mod=998244353;
int n,L,X,Y,l[N],r[N],ql[N],qr[N];
typedef unsigned long long u64;
u64 A,B;
u64 xorshift128p(u64 &A, u64 &B) { 
    u64 T=A,S=B;
    A=S;
    T^=T<<23;
    T^=T>>17;
    T^=S^(S>>26);
    B=T;
    return T+S;
}
void gen(int n,int L,int X,int Y,u64 A,u64 B,int l[],int r[]){ 
    for(int i=1;i<=n;i++){
        l[i]=xorshift128p(A,B)%L+X;
        r[i]=xorshift128p(A,B)%L+Y;
        if(l[i]>r[i])swap(l[i],r[i]);
    }
}
int f[N],ans,nxt[N],head=1,pre[N];
int ksm(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ret;
}
bool vis[N];
signed main(){
    scanf("%lld%lld%lld%lld%llu%llu",&n,&L,&X,&Y,&A,&B);
    gen(n,L,X,Y,A,B,l,r);
    for(re i=1;i<n;i++)nxt[i]=i+1,pre[i+1]=i;
    for(re i=1;i<=n;i++){
        if(!nxt[1])break;
        for(re j=head;j;j=nxt[j]){
            ql[j]=l[j]+1;qr[j]=r[j]-1;
            ql[j]=max(ql[j],max(l[j-1],l[j+1]));
            qr[j]=min(qr[j],min(r[j-1],r[j+1]));
            //cout<<j<<" "<<ql[j]<<" "<<qr[j]<<endl;
        }
        for(re j=head;j;j=nxt[j]){
            l[j]=ql[j],r[j]=qr[j];
            if(qr[j]<ql[j]){
                f[j]=i,vis[j]=true;
                if(j!=head)nxt[pre[j]]=nxt[j],pre[nxt[j]]=pre[j];
                else head=nxt[j];
                //cout<<j<<" "<<f[j]<<endl;
            }
        }
        //cout<<head<<endl;
    }
    for(re i=1;i<=n;i++)ans=(ans+ksm(3,i-1)*f[i]%mod)%mod;//cout<<f[i]<<endl;
    printf("%lld",ans);
}

好了,真好,在zero4338的数据之下,我的代码死成了90,然后又调了一天

真的是很难啊。。。。。

首先我们先会\(\mathcal{O(nlogn)}\)的做法

有一个式子,也就是题解上的式子,不过那个官方的题解真的不是人,直接上了个错的

发现一个规律,如果我用一个中心在当前行的菱形去覆盖,如果无论中心在哪,能覆盖到的点都是1,那么这个是无解的

设菱形的半径为k,那么k步就绝对不可能消掉当前行上的1,为什么是k步,

我们从左端点开始找,先横着走,走到中心所在的列,再一行一行的向下消,所以有这样一个式子:

设中心所在列为y,那么可以完全覆盖的条件为\(y-(k-|i-j|)\ge l_j\),右边同理\(y+(k-|i-j|)\le r_j\)

我们有了这个结论,我们找到最大的k不能消掉当前行,那么k+1就是答案

(我也曾经尝试过找到最小的k可以消掉,但是我失败了)

这个直接把上面的式子拆绝对值,然后变换一下,可以用\(RMQ\)求,直接看看\(y\)可不可能有解,找\(k\)的时候直接二分

这个空间时间都不行。

RMQ+二分
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
const int N=5e6+5;
const int mod=998244353;
int n,L,X,Y,l[N],r[N],ql[N],qr[N];
typedef unsigned long long u64;
u64 A,B;
u64 xorshift128p(u64 &A, u64 &B) { 
    u64 T=A,S=B;A=S; 
    T^=T<<23;T^=T>>17;
    T^=S^(S>>26);B=T;
    return T+S; 
}
void gen(int n,int L,int X,int Y,u64 A,u64 B,int l[],int r[]){ 
    for(int i=1;i<=n;i++){
        l[i]=xorshift128p(A,B)%L+X;
        r[i]=xorshift128p(A,B)%L+Y;
        if(l[i]>r[i])swap(l[i],r[i]);
    }
}
int sl[2][N/5][24],sr[2][N/5][24],lg[N],ans,f[N];
int getl(int x,int y,int typ){
    int t=lg[y-x+1];
    return max(sl[typ][x][t],sl[typ][y-(1<<t)+1][t]);
}
int getr(int x,int y,int typ){
    int t=lg[y-x+1];
    return min(sr[typ][x][t],sr[typ][y-(1<<t)+1][t]);
}
bool check(int y,int z,int k,int i){
    k--;if(i-k<=0||i+k>n)return false;
    int ql0=getl(i-k,i,0)+k-i;
    int qr0=getr(i-k,i,0)-k+i;
    int ql1=getl(i,i+k,1)+k+i;
    int qr1=getr(i,i+k,1)-k-i;
    if(max(ql0,ql1)>min(qr0,qr1))return false;
    if(z-k<max(ql0,ql1)||y+k>min(qr0,qr1))return false;
    return true;
}
int gete(int y,int z,int i,int mn,int mx){
    int l=mn,r=mx,mid;
    while(l<r){
        mid=l+r+1>>1;
        if(check(y,z,mid,i))l=mid;
        else r=mid-1;
    }
    return l;
}
signed main(){
    scanf("%lld%lld%lld%lld%llu%llu",&n,&L,&X,&Y,&A,&B);
    gen(n,L,X,Y,A,B,l,r);
    lg[0]=-1;for(re i=1;i<=n;i++)lg[i]=lg[i>>1]+1;
    for(re i=n,mx;i>=1;i--){
        mx=lg[n-i+1];
        sl[0][i][0]=l[i]+i;
        for(re j=1;j<=mx;j++)sl[0][i][j]=max(sl[0][i][j-1],sl[0][i+(1<<j-1)][j-1]);
        sl[1][i][0]=l[i]-i;
        for(re j=1;j<=mx;j++)sl[1][i][j]=max(sl[1][i][j-1],sl[1][i+(1<<j-1)][j-1]);
    }
    for(re i=n,mx;i>=1;i--){
        mx=lg[n-i+1];
        sr[0][i][0]=r[i]-i;
        for(re j=1;j<=mx;j++)sr[0][i][j]=min(sr[0][i][j-1],sr[0][i+(1<<j-1)][j-1]);
        sr[1][i][0]=r[i]+i;
        for(re j=1;j<=mx;j++)sr[1][i][j]=min(sr[1][i][j-1],sr[1][i+(1<<j-1)][j-1]);
    }
    ans=f[1]=1;
    for(re i=2,bas=3;i<=n;i++,bas=bas*3%mod){
        f[i]=gete(l[i],r[i],i,max(f[i-1]-1,1ll),min(f[i-1]+1,r[i]-l[i]+2>>1));
        ans=(ans+bas*f[i])%mod;
    }
    printf("%lld",ans);
}

然后我们考虑优化:

第一步:\(|f_i-f_{i-1}|\le 1\),所以下次二分的区间就变成\(\mathcal{O(1)}\)的了

第二步:既然\(f\)的差值不大,那么求\(RMQ\)的区间的端点差值也不会很大,

详细的,\(f_i\)的区间左右端点一定在\(f_{i-1}\)的端点上或者右侧

那么就可以直接单调队列搞定了,\([i,i+k]\)这个区间不可以直接加上,因为端点顶多差2,所以这一部分直接循环一下

算完这一行之后,再统一把后面的值插入到单调队列中。。。。

AC_code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
const int N=5e6+5;
const int mod=998244353;
int n,L,X,Y,l[N],r[N],ql[N],qr[N];
typedef unsigned long long u64;
u64 A,B;
u64 xorshift128p(u64 &A, u64 &B) { 
    u64 T=A,S=B;A=S; 
    T^=T<<23;T^=T>>17;
    T^=S^(S>>26);B=T;
    return T+S; 
}
void gen(int n,int L,int X,int Y,u64 A,u64 B,int l[],int r[]){ 
    for(int i=1;i<=n;i++){
        l[i]=xorshift128p(A,B)%L+X;
        r[i]=xorshift128p(A,B)%L+Y;
        if(l[i]>r[i])swap(l[i],r[i]);
    }
}
int s[2][2][N],dl[2][2][N],it[2][2][2],ans,f[N];
void mov0(int x,int y,int t){
    while(dl[0][t][it[0][t][0]]<x&&it[0][t][0]<=it[0][t][1])it[0][t][0]++;
    int beg=dl[0][t][it[0][t][1]]+1;
    for(re i=beg;i<=y;i++){
        while(s[0][t][dl[0][t][it[0][t][1]]]<=s[0][t][i]&&it[0][t][0]<=it[0][t][1])it[0][t][1]--;
        dl[0][t][++it[0][t][1]]=i;
    }while(dl[0][t][it[0][t][0]]<x&&it[0][t][0]<=it[0][t][1])it[0][t][0]++;
}
void mov1(int x,int y,int t){
    while(dl[1][t][it[1][t][0]]<x&&it[1][t][0]<=it[1][t][1])it[1][t][0]++;
    int beg=dl[1][t][it[1][t][1]]+1;
    for(re i=beg;i<=y;i++){
        while(s[1][t][dl[1][t][it[1][t][1]]]>=s[1][t][i]&&it[1][t][0]<=it[1][t][1])it[1][t][1]--;
        dl[1][t][++it[1][t][1]]=i;
    }while(dl[1][t][it[1][t][0]]<x&&it[1][t][0]<=it[1][t][1])it[1][t][0]++;
}
int get0(int x,int y,int w,int t){
    int now=it[w][t][0];
    while(dl[w][t][now]<x&&now<=it[w][t][1])now++;
    return s[w][t][dl[w][t][now]];
}
int get1(int x,int y,int w,int t,int typ){
    int now=it[w][t][0];
    while(dl[w][t][now]<x&&now<=it[w][t][1])now++;
    int ret=s[w][t][dl[w][t][now]];
    for(re i=dl[w][t][it[w][t][1]]+1;i<=y;i++){
        if(!typ)ret=max(ret,s[w][t][i]);
        else ret=min(ret,s[w][t][i]);
    }
    return ret;
}
bool check(int y,int z,int k,int i){
    k--;if(i-k<=0||i+k>n)return false;
    int ql0=get0(i-k,i,0,0)+k-i;
    int qr0=get0(i-k,i,1,0)-k+i;
    int ql1=get1(i,i+k,0,1,0)+k+i;
    int qr1=get1(i,i+k,1,1,1)-k-i;
    if(max(ql0,ql1)>min(qr0,qr1))return false;
    if(z-k<max(ql0,ql1)||y+k>min(qr0,qr1))return false;
    return true;
}
int gete(int y,int z,int i,int mn,int mx){
    int l=mn,r=mx,mid,ret;
    for(mid=l;mid<=r;mid++){
        if(check(y,z,mid,i))ret=mid;
        else break;
    }
    return ret;
}
signed main(){
    scanf("%lld%lld%lld%lld%llu%llu",&n,&L,&X,&Y,&A,&B);
    gen(n,L,X,Y,A,B,l,r);
    for(re i=n,mx;i>=1;i--)s[0][0][i]=l[i]+i,s[0][1][i]=l[i]-i;
    for(re i=n,mx;i>=1;i--)s[1][0][i]=r[i]-i,s[1][1][i]=r[i]+i;
    for(re i=0;i<=1;i++)for(re j=0;j<=1;j++)it[i][j][0]=1;
    mov0(1,2,0);mov1(1,2,0);mov0(2,2,1);mov1(2,2,1);
    ans=f[1]=1;
    for(re i=2,bas=3;i<=n;i++,bas=bas*3%mod){
        f[i]=gete(l[i],r[i],i,max(f[i-1]-1,1ll),min(f[i-1]+1,r[i]-l[i]+2>>1));
        mov0(i+1,max(i+1,i+f[i]-1),1);mov1(i+1,max(i+1,i+f[i]-1),1);
        mov0(max(i-f[i]+1,1ll),i+1,0);mov1(max(i-f[i]+1,1ll),i+1,0);
        ans=(ans+bas*f[i])%mod;
    }
    printf("%lld",ans);
}
posted @ 2021-09-10 19:42  fengwu2005  阅读(124)  评论(1编辑  收藏  举报