2024暑假集訓測試31

卡布叻_周深發表於2024-08-22

前言

  • 比賽連結

image

本來挺水的一場,掛分掛狠了,T1 被 unordered_map 害死了;T2 賽時一看這不 OSU 嘛,反正也會先把部分分打滿回來再寫吧……

T3 只想說出題人三天不罵上房揭瓦,你大樣例鍋了就鍋了能不能說明白,就發一條訊息 “T3 樣例輸出” 總共 \(6\) 個字,鬼知道你說的是大樣例,看一眼小樣例沒換還挺納悶呢。去打 T3,但我不會 AC 自動機啊?拿 kmp 部分分 \(50\) 吧,開不下啊要麼胡個主席樹?胡完了發現不對和暴力一個分,改成純暴力。拍大樣例,過不去啊??不是為啥暴力過不去?ctrl F 啟動,這不對著嗎?哎他是不是換樣例輸出了,看一眼我草真換了,我****。

T4 感覺暴力過 \(3e4\) 都夠嗆,乾脆只開了 \(3e4\) 尋思跑快點,結果拿了 \(50\),賽後看好多 \(70\) 的啊?陣列開大,草就最後倆點過不去,好資料。

不對我** T2 沒寫呢!啊啊啊啊……沒寫完,趕緊胡個暴力得了……

所以加起來掛了 \(140\)(算上 T4 那個 \(20\)),T3 是 AC 自動機板子,可惜當時學的不咋地賽時根本不會,賽後趕緊補補。

T1 線性只因

二進位制按位從大到小維護決策集合即可,若集合內本位為 \(1\) 的個數 \(\ge m\) 則答案加上本位,並將我本位為 \(0\) 的排除決策集合,複雜度 \(O(n\log v)\)

點選檢視程式碼
#include<bits/stdc++.h>
#define ll long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e6+10,M=30;
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);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,m,maxx,ans,tot,cnt,a[N],pos[N];
bool vis[N];
signed main()
{
	read(n,m); 
	for(int i=1,x;i<=n;i++) read(a[i]),maxx=max(maxx,a[i]);
	if(m==1) write(maxx),exit(0);
	for(int i=__lg(maxx);i>=0;i--)
	{
		tot=cnt=0;
		for(int j=1;j<=n;j++) if(!vis[j])
		{
			tot++;
			if(!((a[j]>>i)&1)) pos[++cnt]=j;
		}
		if(tot-cnt>=m) 
		{
			ans|=(1<<i);
			for(int j=1;j<=cnt;j++) vis[pos[j]]=1;
		}
	}
	write(ans);
}

T2 金箱子

  • 類似題:P1654 OSU!

維護 \(x^k\) 就不能只維護 \(x^k\),還要維護 \(x^0,x^1,x^2,……,x^{k-1}\)

然後二項式定理展開直接做就行了,複雜度 \(O(nk^2)\),可以最佳化但我不會,反正能過。

點選檢視程式碼
#include<bits/stdc++.h>
#define ll long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e4+10,M=110,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);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,m,f[2][M],mi[2][M],c[M][M];
int C(int n,int m)
{
	if(m>n) return c[n][m]=0;
	if(m==0||m==n) return c[n][m]=1;
	if(c[n][m]) return c[n][m];
	return c[n][m]=(C(n-1,m)+C(n-1,m-1))%P;
}
int solve(int k,int x[],int y[],int p)
{
	ll ans=0;
	for(int i=0;i<=k;i++) (ans+=1ll*C(k,i)*x[k-i]%P*y[i]%P)%=P;
	return ans*p%P;
}
signed main()
{
	read(n,m);
	f[0][0]=f[1][0]=1;
	for(int i=1,p,a,b;i<=n;i++) 
	{
		read(p,a,b);
		mi[0][0]=mi[1][0]=1;
		for(int j=1;j<=m;j++) 
		{
			mi[0][j]=1ll*mi[0][j-1]*a%P;
			mi[1][j]=1ll*mi[1][j-1]*b%P;
		}
		for(int j=1;j<=m;j++)
			f[i&1][j]=(solve(j,f[(i-1)&1],mi[0],p)+solve(j,f[(i-1)&1],mi[1],P+1-p))%P;
	}
	write(f[n&1][m]);
}

T3 可持久化字串

  • 部分分 \(50pts\):kmp 什麼的直接暴力跑。

  • 正解:

    拓撲最佳化 AC 自動機板子,每次直往後扔一個字元直接扔 trie 樹裡,不用再跑一邊,時空複雜度就保證了,存一下時間戳,等價於求 \(n\) 個模式串在主串中各自出現的次數,拓撲 AC 自動機跑一邊就行了,詢問離線下來,最後字首和一下,複雜度線性。

    點選檢視程式碼
    #include<bits/stdc++.h>
    #define ll long long 
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=1e5+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);
    }
    template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
    template<typename Tp> inline void wt(Tp x)
    {if(x>9)wt(x/10);putchar((x%10)+'0');}
    template<typename Tp> inline void write(Tp x)
    {if(x<0)putchar('-'),x=~x+1;wt(x);}
    template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
    int n,m,cnt,tot,vl[N],vr[N],last[N],en[N],pos[N],deg[N];
    ll sum[N];
    char t[N];
    struct aa {int son[26],fail,id,ans;}trie[N];
    void insert(int p,int id,int c)
    {
        if(!trie[p].son[c]) trie[p].son[c]=++tot;
        p=trie[p].son[c];
        trie[p].id=trie[p].id?trie[p].id:id;
        en[id]=p,pos[id]=trie[p].id;
    }
    void build()
    {
        queue<int>q;
        for(int i=0;i<26;i++) if(trie[0].son[i])
            q.push(trie[0].son[i]);
        while(!q.empty())
        {
            int x=q.front(); q.pop();
            for(int i=0,y,z;i<26;i++)
            {
                y=trie[x].son[i],z=trie[x].fail;
                if(!y) {trie[x].son[i]=trie[z].son[i]; continue;}
                trie[y].fail=trie[z].son[i];
                deg[trie[y].fail]++;
                q.push(y);
            }
        }
    }
    void ask(char s[])
    {
        int p=0,len=strlen(s);
        for(int i=0;i<len;i++)
        {
            p=trie[p].son[s[i]-'a'];
            trie[p].ans++;
        }
    }
    void topo()
    {
        queue<int>q;
        for(int i=0;i<=tot;i++) if(!deg[i]) q.push(i);
        while(!q.empty())
        {
            int x=q.front(); q.pop();
            sum[trie[x].id]=trie[x].ans;
            int y=trie[x].fail;
            deg[y]--,trie[y].ans+=trie[x].ans;
            if(deg[y]==0) q.push(y);
        }
    }
    signed main()
    {
        read(m,n),cin>>t; n=0; char c;
        for(int i=1,op,x;i<=m;i++)
        {
            read(op);
            if(op==1)
            {
                read(x),cin>>c; cnt++;
                insert(en[pos[x]],cnt,c-'a');
                last[cnt]=x;
            }
            if(op==2)
            {
                read(x); cnt++;
                en[cnt]=en[last[x]],pos[cnt]=pos[last[x]];
                last[cnt]=last[last[x]];
            }
            if(op==3) {n++; read(vl[n],vr[n]);}
        }
        build(),ask(t),topo();
        for(int i=1;i<=cnt;i++) sum[i]=en[i]==0?1:sum[pos[i]];
        sum[0]=1; for(int i=1;i<=cnt;i++) sum[i]+=sum[i-1];
        for(int i=1;i<=n;i++)
            write(vl[i]==0?sum[vr[i]]:sum[vr[i]]-sum[vl[i]-1]),puts("");
    }
    

T4 聖誕樹

  • 部分分 \(70pts\):求 lca 然後暴力跳父親,複雜度上限是 \(O(nm)\) 本來只應該得 \(20pts\),出題人應該是故意沒想卡,但是最後兩個點挺強的卡過不去,卡常什麼的可以直接用 bitset。

  • 正解:

    不會整體二分,先不寫了。

    官方題解
    • 對於 \(20\%\) 的資料:

      直接暴力 dfs 統計答案即可。

    • 對於 \(40\%\) 的資料:

      對於每個節點 \(u\) 記錄節點 \(u\) 到根節點路徑上恰好出現了 \(1\) 次的權值,和恰好出現了 \(2\) 次的權值,發現可以透過簡單容斥計算答案,用 bitset 進行維護,複雜度為 \(O(\tfrac{n^2}{w})\)

    • 對於另外 \(10\%\) 的資料:

      由於每個朋友只送給 chino 一件禮物,因此實際上需要求解樹上路徑 \((u,v)\) 中第一個出現次數為 \(0\) 的數,很容易用主席樹上二分求解。

    • 對於 \(70\%\) 的資料:

      考慮操作離線,用樹上莫隊解決,發現操作為查詢第一個未出現的數,用分塊平衡複雜度可以做到 \(O(n\sqrt{n})\)

    • 對於 \(100\%\) 的資料:

      考慮利用每個朋友最多送給 chino 一件禮物這個性質,考慮離線後做整體二分,設當前二分的值為 \(mid\) ,那麼我們需要對於每個詢問求解樹上路徑中 \([L,mid]\) 值域中出現的數的種數。

      發現所有權值的出現情況只有 \(3\) 種:當前權值只出現 \(1\) 次,當前權值出現 \(2\) 次並且出現的位置具有祖先關係,當前權值出現 \(2\) 次並且出現位置沒有祖先關係。

      假設所有詢問 \((x,y)\) 均滿足 \(x\)\(\operatorname{dfn}\) 序小於等於 \(y\)\(\operatorname{dfn}\) 序。

      考慮第一種情況會貢獻到的詢問,設出現的位置為 \(u\) ,容易發現這會貢獻到 \(\operatorname{dfn}_x\in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_u + \operatorname{size}_u, n]\)\(\operatorname{dfn}_x \in [1, \operatorname{dfn}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1]\) 的詢問,需要特殊處理 \(\operatorname{lca}(x, y) = u\) 的詢問。

      考慮第二種情況,首先按照第一種情況分別處理 \(u, v\) 兩點,發現存在一些詢問被貢獻了兩次,設 \(v\)\(u\) 方向上的兒子為 \(p\) ,那麼 \(\operatorname{dfn}_x \in [1, \operatorname{dfn}_p - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1]\)\(\operatorname{dfn}_x \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_p + \operatorname{size}_p, n]\) 的詢問會被貢獻兩次。

      考慮第三種情況,仍然按照第一種情況分別處理 \(u,v\) 兩端,發現 \(\operatorname{dfn}_x \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_v, \operatorname{dfn}_v + \operatorname{size}_v - 1]\) 的詢問會被貢獻兩次。

      於是原問題轉化為二維平面上做矩形加,單點查詢,可以用掃描線解決,複雜度為 \(O(n\log^2n)\) ,使用樹狀陣列後可以在 \(1200ms\) 透過本題。

總結

存在嚴重的時間分配不合理以及不仔細看**出題人的訊息的問題,之前 AC 自動機沒學明白的鍋總算是補上一點了,不然的話這場理應每個人都拿 \(300+\) 的。

附錄

今天開全網報名 CSP 了,還是借別人電話綁郵箱,聽說高二那邊明天基本都要走了?