1. 程式人生 > 實用技巧 >[整理]qbxt週末刷題班 Day1總結

[整理]qbxt週末刷題班 Day1總結

A

Description:把一個\(n\times m\)的字母矩陣裡的每一行每一列拿出來,有\(q\)次單點修改操作,問每次操作後和初始情況下本質不同的字串個數。
Solution:一個樸素的想法是暴力加入\(set\)\(map\),期望得分60分。考場實測60
正解是\(hash\),但是毒瘤出題人卡了單雜湊所以要雙雜湊。
Code:有億些複雜先咕一會

B

Description:有一棵\(n\)個點的樹,求從任意一個點出發,只能走\(a\)步或\(b\)步且不能走之前走過的邊時能到達的點數。多組資料。
幾個部分分:\(a\)\(b\)互質;\(a=b\);一條鏈。
Solution:

由於樹上兩點之間路徑唯一,我們不難想到一個點能夠走到的條件是以起點為根時的深度能被\(a\)\(b\)表出(即\(dist=ax+by\))。
但是正解不是很好想,我們先來看幾個部分分。
\(a=b\):可以用\(f[i][j]\)表示\(i\)子樹裡膜\(a\)\(j\)的點數,然後進行換根dp。
\(a\)\(b\)互質:運用\(NOIP2017\ D1T1\)的結論,只需要考慮深度小於\(ab-a-b\)的點即可。
考慮一般情況:
發現一個點能走到的必要條件是深度為\(\gcd(a,b)\)的倍數。
我們考慮統計一個\(\gcd(a,b)\)倍數的\(f1\)陣列,求出可能走到的點的個數,然後類比\(a\)
\(b\)互質的情況,再統計一個\(f2\)陣列,這樣一減便能求出最終答案。
Implementation:在換根的第二個DFS裡傳入兩個vector分別代表兩組dp值,然後換根。
需要求出一個vis陣列表示每個點是否可達,然後在統計時減掉。
順便放一張老師講的換根dp常見套路:

Code:

#define N 100010
int T,n,a,b,g,mn;
int f1[N][80],f2[N][11],ans1[N][80],ans2[N][80],vis[80];
vector<int>e[N];
inline void ade(int u,int v){
	e[u].pub(v),e[v].pub(u);
}
void DFS(int now,int ff){
	f1[now][0]=f2[now][0]=1;
	for(rg int i=0;i<e[now].size();i++){
		int v=e[now][i];
		if(v!=ff){
			DFS(v,now);
			for(rg int j=1;j<=mn;j++)f1[now][j]+=f1[v][j-1];
			for(rg int j=0;j<g;j++)f2[now][(j+1)%g]+=f2[v][j];
		}
	}
}
void DFS2(int now,int ff,vector<int>v1,vector<int>v2){
	for(rg int i=0;i<=mn;i++){
		ans1[now][i]=f1[now][i]+v1[i];
		v1[i]+=f1[now][i];
	}
	for(rg int i=0;i<g;i++){
		ans2[now][i]=f2[now][i]+v2[i];
		v2[i]+=f2[now][i];
	}
	for(rg int i=0;i<e[now].size();i++){
		int v=e[now][i];
		if(v!=ff){
			for(rg int j=0;j<mn;j++)v1[j+1]-=f1[v][j];
			for(rg int j=0;j<g;j++)v2[(j+1)%g]-=f2[v][j];
			vector<int>n1,n2;n1.resize(mn+1,0),n2.resize(g,0);
			for(rg int j=1;j<=mn;j++)n1[j]=v1[j-1];
			for(rg int j=0;j<g;j++)n2[(j+1)%g]=v2[j];
			DFS2(v,now,n1,n2);
			for(rg int j=0;j<mn;j++)v1[j+1]+=f1[v][j];
			for(rg int j=0;j<g;j++)v2[(j+1)%g]+=f2[v][j];
		}
	}
}
int main(){
	Read(T);
	while(T--){
		for(rg int i=0;i<N;i++)e[i].clear();
		memset(f1,0,sizeof(f1)),memset(f2,0,sizeof(f2)),memset(vis,0,sizeof(vis));
		Read(n),Read(a),Read(b);
		g=__gcd(a,b),mn=max(0,a/g*b/g-a/g-b/g)*g;
		for(rg int i=1;i<n;i++){
			int u,v;Read(u),Read(v),ade(u,v);
		}
		DFS(1,-1);
		vector<int>v1,v2;v1.resize(mn+1,0),v2.resize(g,0);
		DFS2(1,-1,v1,v2);
		vis[0]=1;
		for(rg int i=1;i<=mn;i++){
			if(i>=a)vis[i]|=vis[i-a];
			if(i>=b)vis[i]|=vis[i-b];
		}
		for(rg int i=1;i<=n;i++){
			int res=ans2[i][0];
			for(rg int j=0;j<=mn;j+=g){
				if(!vis[j])res-=ans1[i][j];
			}
			cout<<res<<" ";
		}
		cout<<endl;
	}
	return 0;
}

C

Description:有一個高\(h\)的遊戲機,第\(i\)行有\(i\)個格子,每輪從第一個格子放入一個小球,小球等概率地落到下方兩個格子的任意一箇中。如果小球最終落到的格子中已經有球,那麼遊戲結束,得0分;如果小球落到空格子裡,那麼可以選擇結束遊戲得\(acnt^2+bcnt\)分(其中\(cnt\)為當前輪數),也可以選擇開始新一輪遊戲。問在最優策略下期望能得多少分(保留四位小數)。
Solution:對於前幾種\(h\)較小的情況我們可以暴力列舉方案,但是最大的\(h\)可以達到26,這啟發我們採用狀壓\(dp\)來做。
Implementation:首先用類似楊輝三角的方式算出概率,然後我們設\(f[st]\)為最後一行狀態為\(st\)情況下的期望得分,這時採用記搜來實現這個\(dp\)
具體的搜尋過程是通過列舉下一個球落進的格子來計算出繼續遊戲的期望得分,然後與當前得分比較後存入\(f\)陣列。
Optimization:直接狀壓會爆炸,考慮如何優化狀態壓縮方式。
注意到概率是對稱的,於是我們可以只用一半的狀態來存(例如用00、01、11表示四種狀態)。這樣就可以完美\(AC\)了。
Code:

#define N 30
int T,n,a,b;
double C[N][N];
inline void Init(){
	C[1][1]=1;
	for(rg int i=2;i<N;i++){
		for(rg int j=1;j<=i;j++){
			C[i][j]=(C[i-1][j]+C[i-1][j-1])/2;
		}
	}
}
double f[70000000];
double DP(int st){
	if(f[st]>=0)return f[st];
	int cnt=__builtin_popcount(st);
	double bonus_now=a*cnt*cnt+b*cnt,bonus_con=0.0;
//	stop now / continue
	for(rg int i=1;i<=n;i++){
		if(st>>(i-1)&1)continue;//you lose the game
		int st_new=st;
		if(n-i<i&&!(st>>(n-i)&1))st_new^=1<<(n-i);
		else st_new^=1<<(i-1);
		bonus_con+=C[n][i]*DP(st_new);
	}
//	cout<<bonus_now<<" "<<bonus_con<<endl;
	return f[st]=max(bonus_now,bonus_con);
}
int main(){
	Read(T);Init();
	while(T--){
		Read(n),Read(a),Read(b);
		for(rg int i=0;i<1<<n;i++)f[i]=-1;
		printf("%.4lf\n",DP(0));
	}
	return 0;
}

D

Description:

Solution:\(f[i][j]\)為當前在第\(i\)列,要從第\(j\)行出去的方案數。
我們可以列舉當前一行從哪一行轉移來,有障礙物的不能轉移。
對於\(Q\le 10^5\)的資料,我們可以把狀態轉移寫成矩陣,然後加一個線段樹進行單點修改即可。
對於\(M\le 10^9\)的資料,我們可以離散化一下。
狀態轉移(無障礙物):

\[\begin{bmatrix} 0&1&0&0\\1&0&1&0\\0&1&0&1\\0&0&1&0 \end{bmatrix} \begin{bmatrix} f[i][1]\\f[i][2]\\f[i][3]\\f[i][4] \end{bmatrix} = \begin{bmatrix} f[i+1][1]\\f[i+1][2]\\f[i+1][3]\\f[i+1][4] \end{bmatrix} \]

Notice:此題嚴重卡常,我的程式碼需新增#pragma GCC optimize(2)才可過。
Code:

#define mod 998244353
#define Q 200010
int n,m,q,x[Q],y[Q],st[Q];
vector<int>pos;
struct Matrix {
	int r,c;
	int a[4][4];
	Matrix(int _r=0,int _c=0){
		r=_r,c=_c;
		memset(a,0,sizeof(a));
	}
};
inline Matrix Mul(Matrix A,Matrix B){
	Matrix C(A.r,B.c);
	for(rg int i=0;i<A.r;i++){
		for(rg int j=0;j<B.c;j++){
			LL tmp=0;
			for(rg int k=0;k<A.c;k++){
				tmp+=1ll*A.a[i][k]*B.a[k][j];
			}
			C.a[i][j]=tmp%mod;
		}
	}
	return C;
}
inline Matrix Pow(Matrix A,int b){
	Matrix ans(A.r,A.r);
	for(rg int i=0;i<A.r;i++)ans.a[i][i]=1;
	while(b){
		if(b&1){
			ans=Mul(ans,A);
		}
		A=Mul(A,A);
		b>>=1;
	}
	return ans;
}
Matrix tran[16],mat[Q];
struct Node {
	int l,r;
	Matrix wei;
}tr[Q<<2];
inline void Pushup(int k){
	tr[k].wei=Mul(tr[ls].wei,tr[rs].wei);
}
void Build(int k,int l,int r){
	tr[k].l=l,tr[k].r=r;
	if(l==r){
		tr[k].wei=mat[l];
		return;
	}
	int mid=nmid;
	Build(ls,l,mid);Build(rs,mid+1,r);
	Pushup(k);
}
void ModifyPoint(int k,int pos,Matrix num){
	if(Thispoint){
		tr[k].wei=num;
		return;
	}
	int mid=tmid;
	if(pos<=mid)ModifyPoint(ls,pos,num);
	else ModifyPoint(rs,pos,num);
	Pushup(k);
}
int main(){
	Read(n),Read(m),Read(q);
	for(rg int i=1;i<=q;i++){
		Read(x[i]),Read(y[i]);
		pos.pub(y[i]),pos.pub(y[i]+1);
	}
	pos.pub(1),pos.pub(m+1);
	sort(pos.begin(),pos.end());
	pos.resize(unique(pos.begin(),pos.end())-pos.begin());
	for(rg int i=0;i<(1<<n);i++){
		tran[i].r=tran[i].c=n;
		for(rg int j=0;j<n-1;j++){
			if((i>>j&1)||(i>>(j+1)&1))continue;
			tran[i].a[j][j+1]=tran[i].a[j+1][j]=1;
		}
	}
	int tot=pos.size();
	for(rg int i=0;i<tot-1;i++){
		mat[i]=Pow(tran[0],pos[i+1]-pos[i]);
	}
	Build(1,0,tot-2);
	for(rg int i=1;i<=q;i++){
		int p=lower_bound(pos.begin(),pos.end(),y[i])-pos.begin();
		st[p]|=1<<(x[i]-1);
		ModifyPoint(1,p,tran[st[p]]);
		cout<<tr[1].wei.a[0][n-1]<<endl;
	}
	return 0;
}

完結撒花