前言
- 比賽連結。
本來挺水的一場,掛分掛狠了,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 了,還是借別人電話綁郵箱,聽說高二那邊明天基本都要走了?