2024暑假集訓測試6

卡布叻_周深發表於2024-07-20

前言

  • 比賽連結

image

掛分掛的最多的一集。

T1 不知道摩爾投票,被 2M 記憶體限制卡死。

T2 賽時打了個很像正解的莫隊,賽時出題人發現了之後現往裡加 hack,還一個捆綁里加一個,直接爆零了,我真的謝了,求求以後不要一個捆綁放一個 hack 了,給條活路吧。

T3 一眼看出線段樹最佳化建圖,但是不會打。

出題人說這次比賽就是為了讓我們漲漲見識,學一些板子和套路。

T1 活動投票

摩爾投票,若新的 \(a_i\) 與當前答案相同,則 \(sum+1\),否則 \(sum-1\)\(sum=0\) 時直接更新答案為當前 \(a_i\),正確性顯然。

T2 序列

  • 部分分 \(30pts\):暴力沒什麼好說的。

  • 假做法:

    對於每個 \(a_i\) 有其 \(last_i\) 表示其上一次出現的位置,對於每個 \(last_i\ne 0\)\(i\),另 \(l=last_i,r=i\) 最為一組詢問跑莫隊,若該段詢問區間裡均存在只出現過一次的就合法,否則不合法。

    考慮做法的錯誤性,由於只考慮了 \(i\)\(last_i\) 之間,進而沒有考慮到一段子串中出現 \(3\) 次及以上的情況。

    提供一組 \(hack\)1 2 1 2 1

    考慮最佳化成 \(last_i\sim next_i\) 的,由於莫隊複雜度擦邊且存在一定常數,仍會 \(TLE\),沒有繼續深入研究,這個演算法最多隻能騙 \(60pts\) 了。

  • 正解:

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

    列舉右端點 \(r\)\(f_i\) 表示區間 \([i,r]\) 存在只出現一次的數的個數,考慮當 \(r+1\) 時如何轉移。

    \(a_{r+1}\) 之前沒有出現過,則 \(f_1\sim f_{r+1}\)\(+1\)

    否則定義 \(last_{r+1}\) 表示 \(a_{r+1}\) 上一次出現的位置,\(f_{last_{r+1}+1}\sim f_{r+1}\)\(+1\)\(f_{last_{last_{r+1}}+1}\sim f_{last_{r+1}}\)\(-1\)

    合法的條件就是任意時刻 \(f_1\sim f_{r}\)\(\ge 1\)

    修改可以用線段樹區間修改,查詢可以線段樹查詢區間最小值。

    點選檢視程式碼
    #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

  • \(CF\) 上有同名原題,多倍經驗 luogu P6348 [PA2011] Journeys

  • 部分分直接暴力建圖就好了。

線段樹最佳化建圖板子。

  • 建樹:

    建兩棵樹,一顆出樹一顆入樹。

    對於入樹,若該節點被連線則其子節點一定被連線,所以每個點向子節點連權值為 \(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\)

定義 \(f_{i,j,k}\) 表示當前填的數為 \(i\),存在 \(j+1\) 個段,其和為 \(k\) 有多少情況,另其強制從小到大填數,初始值 \(f_{1,0,0}=1\)

對於新填一個數對於 \(k\) 的貢獻,有:

  • \(i\) 與左右兩邊均不相鄰,其後面填的數一定 \(>i\),故不產生貢獻。

  • \(i\) 與左右兩邊中的一個相鄰,\(i\) 一定 \(>\) 之前填的數,其後面填的數一定 \(>i\),故貢獻為 \(i\)

  • \(i\) 與左右兩邊均相鄰,\(i\) 一定 \(>\) 之前填的數,故貢獻為 \(2\times i\)

因此有轉移:

  • 若填在原序列兩端:

    • 若與原序列不相鄰:

      會產生一個新的段,同時不產生貢獻,兩端有兩種可能,有 \(f_{i,j+1,k}+=f_{i-1,j,k}\times 2\)

    • 若與原序列相鄰:

      不會產生新的段,產生 \(i\) 的貢獻,兩端有兩種可能,有 \(f_{i,j,k+i}+=f_{i-1,j,k}\times 2\)

  • 若填在原序列之間:

    • 與原序列不存在相鄰:

      會產生一個新的段,對答案不產生貢獻,\(j+1\) 個段有 \(j\) 個空,有 \(f_{i,j+1,k}+=f_{i-1,j,k}\times j\)

    • 與原序列一端相鄰:

      不會產生新的段,對答案產生 \(i\) 的貢獻,\(j+1\) 個段有 \(j\) 個空,與其任意一端相鄰有兩種情況,有 \(f_{i,j,k+i}+=f_{i-1,j,k}\times j\times 2\)

    • 與原序列兩端均相鄰:

      將兩端接壤,故減少一個段,對答案產生 \(2\times i\) 的貢獻,\(j+1\) 個段有 \(j\) 個空,有 \(f_{i,j-1,k+2\times i}+=f_{i-1,j,k}\times j\)

最後答案 \(ans=\sum\limits_{i=0}^mf_{n,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 的套路。

相關文章