2024暑假集训测试6

前言

image

挂分挂的最多的一集。

T1 不知道摩尔投票,被 2M 内存限制卡死。

T2 赛时打了个很像正解的莫队,赛时出题人发现了之后现往里加 hack,还一个捆绑里加一个,直接爆零了,我真的谢了,求求以后不要一个捆绑放一个 hack 了,给条活路吧。

T3 一眼看出线段树优化建图,但是不会打。

出题人说这次比赛就是为了让我们涨涨见识,学一些板子和套路。

T1 活动投票

摩尔投票,若新的 ai 与当前答案相同,则 sum+1,否则 sum1sum=0 时直接更新答案为当前 ai,正确性显然。

T2 序列

  • 部分分 30pts:暴力没什么好说的。

  • 假做法:

    对于每个 ai 有其 lasti 表示其上一次出现的位置,对于每个 lasti0i,另 l=lasti,r=i 最为一组询问跑莫队,若该段询问区间里均存在只出现过一次的就合法,否则不合法。

    考虑做法的错误性,由于只考虑了 ilasti 之间,进而没有考虑到一段子串中出现 3 次及以上的情况。

    提供一组 hack1 2 1 2 1

    考虑优化成 lastinexti 的,由于莫队复杂度擦边且存在一定常数,仍会 TLE,没有继续深入研究,这个算法最多只能骗 60pts 了。

  • 正解:

    正解有很多种,我只改了一种,也是很套路的一种。

    枚举右端点 rfi 表示区间 [i,r] 存在只出现一次的数的个数,考虑当 r+1 时如何转移。

    ar+1 之前没有出现过,则 f1fr+1+1

    否则定义 lastr+1 表示 ar+1 上一次出现的位置,flastr+1+1fr+1+1flastlastr+1+1flastr+11

    合法的条件就是任意时刻 f1fr1

    修改可以用线段树区间修改,查询可以线段树查询区间最小值。

    点击查看代码
    #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    #define sort stable_sort
    #define f t[p]
    #define ls p<<1
    #define rs p<<1|1
    using namespace std;
    const int N=2e5+10;
    template<typename Tp> inline void read(Tp&x)
    {
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
    }
    void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
    void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
    int T,n,a[N],b[N],last[N],old[N],now[N],cnt[N];
    struct aa {int l,r,val,add;}t[N<<2];
    void build(int p,int l,int r)
    {
    f.l=l,f.r=r,f.val=f.add=0;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(ls,l,mid),build(rs,mid+1,r);
    f.val=min(t[ls].val,t[rs].val);
    }
    void spread(int p)
    {
    if(f.add==0) return ;
    t[ls].val+=f.add,t[ls].add+=f.add;
    t[rs].val+=f.add,t[rs].add+=f.add;
    f.add=0;
    }
    void change(int p,int l,int r,int d)
    {
    if(l>r) return ;
    if(l<=f.l&&r>=f.r)
    {
    f.val+=d,f.add+=d;
    return ;
    }
    spread(p);
    int mid=(f.l+f.r)>>1;
    if(l<=mid) change(ls,l,r,d);
    if(r>mid) change(rs,l,r,d);
    f.val=min(t[ls].val,t[rs].val);
    }
    int ask(int p,int l,int r)
    {
    if(l<=f.l&&r>=f.r) return f.val;
    spread(p);
    int mid=(f.l+f.r)>>1,ans=0x3f3f3f3f;
    if(l<=mid) ans=min(ans,ask(ls,l,r));
    if(r>mid) ans=min(ans,ask(rs,l,r));
    return ans;
    }
    signed main()
    {
    read(T);
    while(T--)
    {
    read(n);
    for(int i=1;i<=n;i++)
    read(a[i]),
    b[i]=a[i],
    cnt[i]=now[i]=last[i]=old[i]=0;
    sort(b+1,b+1+n);
    b[0]=unique(b+1,b+1+n)-(b+1);
    for(int i=1;i<=n;i++)
    a[i]=lower_bound(b+1,b+1+b[0],a[i])-b;
    for(int i=1;i<=n;i++)
    {
    last[i]=now[a[i]];
    old[i]=last[last[i]];
    now[a[i]]=i;
    }
    build(1,1,n);
    bool flag=0;
    for(int i=1;i<=n;i++)
    {
    if(cnt[a[i]]==0)
    {
    change(1,1,i,1);
    if(ask(1,1,i)<=0)
    {
    flag=1;
    puts("boring");
    break;
    }
    }
    else
    {
    change(1,last[i]+1,i,1);
    change(1,old[i]+1,last[i],-1);
    if(ask(1,1,i)<=0)
    {
    flag=1;
    puts("boring");
    break;
    }
    }
    cnt[a[i]]++;
    }
    if(!flag) puts("non-boring");
    }
    }

T3 Legacy

线段树优化建图板子。

  • 建树:

    建两棵树,一颗出树一颗入树。

    对于入树,若该节点被连接则其子节点一定被连接,所以每个点向子节点连权值为 0 的边。

    对于出树,若该节点可向外连接则其父节点也一定能向外连接,故每个点向其父节点连权值为 0 的边。

    进了该点后就要出点,于是入树上每个节点向出树上对应节点连权值为 0 的边。

  • 连边:

    只考虑区间连区间这种更一般的情况,点就是长度为 1 的区间。

    对于区间 [a,b][c,d],定义一个新的虚点 x,在出树上另 [a,b]x,再另 x 连入树上 [c,d],考虑权值不可重复计算,两次操作中仅让一个带权值,另一个权值为 0

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls (p<<1)
#define rs (p<<1|1)
using namespace std;
const int N=2e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,cnt,s,pos[N],dis[N];
bool vis[N];
int tot,head[N],to[N],nxt[N],w[N];
struct aa {int l,r;}t[N];
void add(int x,int y,int z)
{
nxt[++tot]=head[x];
to[tot]=y;
w[tot]=z;
head[x]=tot;
}
void build(int p,int l,int r)
{
f.l=l,f.r=r;
add(p+4*n,p,0);
if(l==r) {pos[l]=p; return ;}
add(ls,p,0),add(rs,p,0);
add(p+4*n,ls+4*n,0),add(p+4*n,rs+4*n,0);
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
}
void change(int p,int x,int l,int r,int z,bool d)
{
if(l<=f.l&&r>=f.r)
{
if(d==0) add(p,x+8*n,0);
else add(x+8*n,p+4*n,z);
return ;
}
int mid=(f.l+f.r)>>1;
if(l<=mid) change(ls,x,l,r,z,d);
if(r>mid) change(rs,x,l,r,z,d);
}
void add(int a,int b,int c,int d,int z)
{
cnt++;
change(1,cnt,a,b,z,0);
change(1,cnt,c,d,z,1);
}
void dij(int s)
{
memset(dis,0x3f,sizeof(dis));
priority_queue<pair<int,int>>q;
dis[s]=0;
q.push(make_pair(0,s));
while(!q.empty())
{
int x=q.top().second;
q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i],z=w[i];
if(dis[y]>dis[x]+z)
{
dis[y]=dis[x]+z;
q.push(make_pair(-dis[y],y));
}
}
}
}
signed main()
{
read(n),read(m),read(s);
build(1,1,n);
for(int i=1,op,z,a,b,c,d;i<=m;i++)
{
read(op);
if(op==1)
read(a),read(c),read(z),
b=a,d=c;
if(op==2)
read(a),read(c),read(d),read(z),
b=a;
if(op==3)
read(c),read(a),read(b),read(z),
d=c;
add(a,b,c,d,z);
}
dij(pos[s]);
for(int i=1;i<=n;i++)
if(i==s) write(0),putchar(' ');
else write(dis[pos[i]+n*4]==0x3f3f3f3f3f3f3f3f?-1:dis[pos[i]+n*4]),putchar(' ');
}

T4 DP搬运工1

预设性 DP,计数 DP

定义 fi,j,k 表示当前填的数为 i,存在 j+1 个段,其和为 k 有多少情况,另其强制从小到大填数,初始值 f1,0,0=1

对于新填一个数对于 k 的贡献,有:

  • i 与左右两边均不相邻,其后面填的数一定 >i,故不产生贡献。

  • i 与左右两边中的一个相邻,i 一定 > 之前填的数,其后面填的数一定 >i,故贡献为 i

  • i 与左右两边均相邻,i 一定 > 之前填的数,故贡献为 2×i

因此有转移:

  • 若填在原序列两端:

    • 若与原序列不相邻:

      会产生一个新的段,同时不产生贡献,两端有两种可能,有 fi,j+1,k+=fi1,j,k×2

    • 若与原序列相邻:

      不会产生新的段,产生 i 的贡献,两端有两种可能,有 fi,j,k+i+=fi1,j,k×2

  • 若填在原序列之间:

    • 与原序列不存在相邻:

      会产生一个新的段,对答案不产生贡献,j+1 个段有 j 个空,有 fi,j+1,k+=fi1,j,k×j

    • 与原序列一端相邻:

      不会产生新的段,对答案产生 i 的贡献,j+1 个段有 j 个空,与其任意一端相邻有两种情况,有 fi,j,k+i+=fi1,j,k×j×2

    • 与原序列两端均相邻:

      将两端接壤,故减少一个段,对答案产生 2×i 的贡献,j+1 个段有 j 个空,有 fi,j1,k+2×i+=fi1,j,k×j

最后答案 ans=i=0mfn,0,i

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=55,P=998244353;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,ans,f[N][N][N*N];
signed main()
{
read(n),read(m);
f[1][0][0]=1;
for(int i=2;i<=n;i++)
for(int j=0;j<=n-i+1;j++)
for(int k=0;k<=m;k++)
{
(f[i][j+1][k]+=f[i-1][j][k]*2)%=P;
(f[i][j][k+i]+=f[i-1][j][k]*2)%=P;
if(j!=0)
(f[i][j+1][k]+=f[i-1][j][k]*j)%=P,
(f[i][j][k+i]+=f[i-1][j][k]*j*2)%=P,
(f[i][j-1][k+i*2]+=f[i-1][j][k]*j)%=P;
}
for(int i=0;i<=m;i++) (ans+=f[n][0][i])%=P;
write(ans);
}

总结

像 T1 这样怎么想都过不去的就不要抱有侥幸心理,而是去想新做法。

想到做法时先想想会不会被 hack。

多积累一些板子和套路,类似数颜色类型的区间特点统计的题考虑类似 T2 的套路。

posted @   卡布叻_周深  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示