24暑集訓Week1
夜行的人,若你不唱歌的話,不驚醒這黑夜的話,就永遠也走不出呼藍別斯了。 這重重的森林,這崎嶇纖細的山路,這孤獨疲憊的心。 親愛的,哪怕後來去到了城市,$$走$$夜路時也要大聲地唱歌,像喝醉酒的人一樣無所顧忌。 大聲地唱啊,讓遠方的大棕熊也聽到了,也靜靜起身,為你在遙遠的地方讓路。 ——李娟《走夜路請放聲歌唱》
【2024.07.22】NOIP2024暑假集訓模擬賽(1)
A
- 分數規劃模板題,見 我的這篇部落格.
B
- 抽 \(n\) 次卡, 連續 \(i\) 次沒有抽中時, 第 \(i+1\) 次抽中的機率是 \(p_i\), 規定\(p_k=1\), 求期望抽中次數.
- 標籤:矩陣加速遞推, 動態規劃.
- 暴力: 記 \(f[i][j]\) 表示已經抽了 \(i\) 次, 目前連續 \(j\) 次不中的期望抽中次數,有轉移:
-
時間複雜度 \(O(NK)\).
-
最佳化:矩陣加速遞推
-
\[\begin{bmatrix} p_0 & p_1 & p_2 & \cdots & p_k & 0 \\ 1-p_0 & 0 & 0 & \cdots & 0 & 0 \\ 0 & 1-p_1& 0 &\cdots & 0 & 0 \\ 0 & 0 & 1-p_2&\cdots & 0 & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots & \vdots\\ 0 & 0 & \cdots & 1-p_{k-1} & 0 & 0\\ 0 & 0 & \cdots & 0 & 0 & 0 \\ p_0 & p_1 & p_2 & \cdots & p_k & 1 \\ \end{bmatrix} \times \begin{bmatrix} f_{0} \\ f_{1} \\ f_{2} \\ \vdots \\ f_{k} \\ res \\ \end{bmatrix} \]
-
記矩陣為 \(A\)(一個 邊長為 \(k+2\) 的方陣), 列向量為 \(F_0\)(其中 \(f_i=1,f_{1 \sim k}=0\))
-
先用矩陣快速冪求出 \(A^n \times F_0\), 答案就是 \(res\)
-
時間複雜度:本來是\(O(K^3 \times log_2^N)\), 但有一個小最佳化是每次
y&1=1
的時候不要另開一個單位矩陣存答案, 直接累計到那個行向量裡面,這樣時間複雜度會有一個 \(\frac{1}{2}\) 的常數, 會快一倍
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=r;++i)
#define G(i,r,l) for(int i(r);i>=l;--i)
using namespace std;
using ll = long long;
const int mod=998244353;
int a[205],b[205],p[205];
int n,k;
struct matrix{
int a[205][205];
void init(int val=0){
memset(a,0,sizeof(a));
F(i,0,k+1) a[i][i]=val;
}
}v;
matrix operator $ (matrix A,matrix B){
matrix C; C.init(0);
F(i,0,k+1) F(j,0,k+1) F(z,0,k+1) C.a[i][z]=(C.a[i][z]+1ll$A.a[i][j]$B.a[j][z]%mod)%mod;
return C;
}
void ksm(matrix A,int b){
int f[205],g[205];
memset(f,0,sizeof(f));
f[0]=1;
while(b){
if(b&1){
memset(g,0,sizeof(g));
F(i,0,k+1) F(j,0,k+1) g[i]=(g[i]+1ll$A.a[i][j]$f[j])%mod;//最佳化在這裡,單次變成K^2
memcpy(f,g,sizeof(f));
}
A=A$A;
b>>=1;
}
cout<<(f[k+1]%mod+mod)%mod;
}
int quickmod(int x,int y){
int res=1;
while(y){
if(y&1) res=1ll$res$x%mod;
x=1ll$x$x%mod;
y>>=1;
} return res;
}
signed main(){
freopen("card.in","r",stdin);
freopen("card.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>n>>k;
F(i,0,k-1){
cin>>a[i]>>b[i];
p[i]=1ll$a[i]$quickmod(b[i],mod-2)%mod;
} p[k]=1;
v.init(0);
F(i,0,k) v.a[0][i]=v.a[k+1][i]=p[i];
F(i,1,k) v.a[i][i-1]=1-p[i-1];
v.a[k+1][k+1]=1;
ksm(v,n);
return 0;
}
C
- 線性基
D
- 點分治
【2024.07.24】NOIP2024暑假集訓模擬賽(2)
A
-
一句話題意:給定 \(n,T,s_i\) , 詢問有多少個四元組 \((a,b,c,d)\) 滿足 \(\lfloor \frac{s_a}{s_b} \rfloor + \lfloor \frac{s_c}{s_d} \rfloor = T\), \(n,s_i,T \le 1e6\).
-
先列舉分母是整除分塊,時間複雜度 \(O(N\sqrt N)\)
據說drz卡過去了; 先列舉分子是調和級數, 時間複雜度 \(O(Nlog N)\).
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=r;++i)
#define G(i,r,l) for(int i(r);i>=l;--i)
using namespace std;
using ll = long long;
const int N=1e6+3;
const int mod=998244353;
char buf[100],$p1=buf,$p2=buf;
inline int gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100,stdin),p1==p2)?EOF:$p1++;}
inline int rd(){
int x=0; char ch;
while(!isdigit(ch=gc()));
do x=(x<<3)+(x<<1)+(ch^48); while(isdigit(ch=gc()));
return x;
}
int n,x,m=0,ans=0;
int a[N],num[N],sum[N],t[N];
signed main(){
freopen("floor.in","r",stdin);
freopen("floor.out","w",stdout);
n=rd(),x=rd();
F(i,1,n) num[a[i]=rd()]++;
F(i,1,1000000) sum[i]=sum[i-1]+num[i];
// a/b=0
F(i,1,1000000) t[0]=(t[0]+1ll$sum[i-1]$num[i])%mod;
// a/i = j
F(i,1,1000000){
for(int j=1;j$i<=1000000;++j){
t[j] = (1ll$t[j]+(1ll$sum[min(1000000,i$(j+1)-1)]-sum[i$j-1]) $ num[i]%mod)%mod;
}
}
F(i,0,x) ans=(ans+1ll$t[i]$t[x-i]%mod)%mod;
printf("%d",(ans+mod)%mod);
return 0;
}
B
- 異或
C
- 線段樹,滑動視窗
D
- CDQ分治, 線段樹
【2024.07.26】NOIP2024暑假集訓模擬賽(3)
A
- 考慮什麼樣的操作合法:
- k 一定是 \(mex(a)\).
- 所有值為 \(mex(a)+1\) 的位置必須被覆蓋。
- 對於所有 \(i\in[0,mex(a)−1]\) 的位置不能全被覆蓋。
如果 \(a\) 中沒有 \(m\)\(e\)\(x\)(\(a\))+1,那隻要\(mex(a)\) 不為 \(n\) 就總能找到一個不用的位置賦值。
否則,我們記錄值為 \(mex(a)+1\) 的最靠前和最靠後的位置,覆蓋,然後判斷是否符合 \(3\) 即可。
\(B\)
- 記每個位置的權值為 \(i \times a_i\) , 記總和為 \(S\), 1操作不會使 \(S\) 改變, 每次2操作 \(S+1\).
- 所以只有那個唯一使用 2操作的 \(S\) 不同, \(\Delta S\) 即為 2操作次數.
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=r;++i)
#define G(i,r,l) for(int i(r);i>=l;--i)
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define eb emplace_back
#define int long long
using namespace std;
using ll = long long;
int T,m,n;
signed main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>T; while(T--){
cin>>m>>n;
int ws1=-1,ws2=-1,num1=0,num2=0,ps1=-1,ps2=-1,x;
F(i,1,m){
int ws=0;
F(j,1,n) cin>>x,ws+=j*x;
if(ws1==-1) ws1=ws,num1=1,ps1=i;
else if(ws1==ws) ++num1;
else if(ws2==-1) ws2=ws,num2=1,ps2=i;
else if(ws2==ws) ++num2;
}
if(num1>1) cout<<ps2<<" "<<ws2-ws1<<"\n";
else cout<<ps1<<" "<<ws1-ws2<<"\n";
}
return 0;
}
C
-
給定 \(n\) 個點,每個點上有一個長為 \(m\) 的字串(僅包含字母 \(Y\) 和 \(N\) ),兩個點之間的邊權為兩個字串不同元素個數,求最小生成樹邊權和。
-
用2進位制預處理所有的字串, 記 \(f[i][j]\) 表示到 \(i\) 距離為 \(j\) 的一個點(任一個點都可以,因為每次搜尋完當前位, \(f[i][j]\) 都會更新(見程式碼), 同樣優的點會在前面被用到(大概是這樣吧), 所以任記錄一個就行)
-
折半搜尋, 每次列舉一對 \((a,b)\) 使 $dis(a,i) = j , dis(b,i) = j , dis(a,b)=1 $, 即列舉一條權值為 \(1\) 的邊.
D
- 線段樹
【2024.07.27】NOIP2024暑假集訓模擬賽(4)
B
- 字串模擬
C
D
- 線段樹,dp