BNDS 2024/4/6模擬賽題解

perryzhou發表於2024-04-06

T1 方程

描述

給出非負整數 \(N\) ,統計不定方程 \(X+Y^2+Z^3=N\) 的非負整數解 \((X,Y,Z)\) 的數量。

輸入

輸入資料,包含一個非負整數 \(N\)

輸出

輸出資料,包含一個非負整數表示解的數量。

資料範圍

40%的資料,\(N<=10000\)
60%的資料,\(N<=10^8\)
100%的資料,\(N<=10^{16}\)

分析

看到這個資料範圍,應該只能從 \(Z\) 開始列舉。

移項後可得 \(X+Y^2=N-Z^3\) ,所以 \(0\leq Y\leq\sqrt{N-Z^3}\)

注意,我們對於這個 \(Y\) 的具體值不感興趣,我們只關心有多少個,而此時有 \((\sqrt{N-Z^3}+1)\) 個合法的 \(Y\)。此時, \(X=N-Z^3-Y^2\) ,顯然也是一個非負整數。

所以,我們只需要從小到大列舉 \(Z\),將 \((\sqrt{N-Z^3}+1)\) 新增到 \(ans\) 即可。

時間複雜度 \(O(\sqrt[3]{n})\)(sqrt視作常數複雜度)。

程式碼

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
ll n,ans;
int main(){
	scanf("%lld",&n);
	for(ll z=0;z*z*z<=n;z++)
		ans+=ll(sqrt(n-z*z*z))+1ll;
	
	printf("%lld",ans);
	return 0;
	
}

T2 打磚塊

描述

在一個凹槽中放置了 \(n\) 層磚塊,最上面的一層有 \(n\) 塊磚,從上到下每層依次減少一塊磚。每塊磚都有一個分值,敲掉這塊磚就能得到相應的分值,如下圖所示。

如果你要敲掉第 \(i\) 層的第 \(j\) 塊磚的話,若 \(i=1\),你可以直接敲掉它;若 \(i>1\),則你必須先敲掉第 \((i-1)\) 層的第 \(j\) 和第 \(j+1\) 塊磚。

你現在可以敲掉最多m塊磚,求得分最多能有多少。

輸入

輸入的第一行為兩個正整數 \(n,m\);接下來 \(n\) 行,描述這 \(n\) 層磚塊上的分值 \(a[i][j]\),滿足 \(0\leq a[i][j] \leq 100\)

輸出

輸出僅一行為一個正整數,表示被敲掉磚塊的最大價值總和。

資料範圍

對於 \(20\%\) 的資料,滿足\(1\leq n\leq 10, 1\leq m\leq 30\)
對於 \(100\%\) 的資料,滿足\(1\leq n\leq 50, 1\leq m\leq 500\)

分析

一眼DP。

不過,看圖之後發現,這個圖並不存在決策單調性的性質。
不過旋轉之後,就可以變成前 \(i\) 行與前 \(j\) 列的dp了。
這裡透過一個輸入就能完成旋轉:

for(int j=1;j<=n;j++){
	for(int i=n;i>=j;i--){
		scanf("%d",&brick[i][j]);
	}
}

\(dp[i][j][k]\) 代表前 \(i\) 行中,第 \(i\) 行選了 \(j\) 個、一共選了 \(k\) 個的最大值,然後進行dp即可。

還有就是記得提前算每行的字首和\(sum[i][j]\),最佳化速度。

PS:這道題的限制條件挺多的...所以寫出了諸如 \(mxg, mxj, mxk\) 之類的抽象玩意。
\(g\) 是總共選擇了多少,不能超過 \(\sum\limits_{a=1}^i a\) 的同時也不能超過 \(m\)
\(j\) 不能超過 \(i\)(不能無中生有是吧),也不能超過 \(g\)
\(k\) 同理,不能超過 \(i-1\) 的同時也不能超過 \(g-j\)

最壞時間複雜度 \(O(nm^3)\),實際上遠遠跑不到。

程式碼

#include<bits/stdc++.h>
#define N 55
using namespace std;
int n,m,brick[N][N],dp[N][N][2005],sum[N][N];
int ans;
int main(){
	scanf("%d%d",&n,&m);
	for(int j=1;j<=n;j++){
		for(int i=n;i>=j;i--){
			scanf("%d",&brick[i][j]);
		}
	}
	/*
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cout<<brick[i][j]<<' ';
		}
		cout<<endl;
	}*/
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			sum[i][j]=sum[i][j-1]+brick[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		int mxg=min((i*(i+1))>>1,m),mxj,mxk;
		for(int g=0;g<=mxg;g++){
			mxj=min(i,g);
			for(int j=0;j<=mxj;j++){
				mxk=min(g-j,i-1);
				for(int k=max(j-1,0);k<=mxk;k++){
					if(dp[i-1][k][g-j]+sum[i][j]>dp[i][j][g])
						dp[i][j][g]=dp[i-1][k][g-j]+sum[i][j];
				}
			}
		}
	}
	for(int j=0;j<=n;j++){
		if(dp[n][j][m]>ans)
			ans=dp[n][j][m];
	}
	printf("%d",ans);
	return 0;
}  

T3 打磚塊

描述

有兩個長度為 \(N\) 的序列 \(A\)\(B\),在 \(A\)\(B\) 中各任取一個數相加可以得到 \(N^2\) 個和,求這 \(N^2\) 個和中最小的 \(N\) 個。

輸入

第一行輸入一個正整數 \(N\) ;第二行 \(N\) 個整數 \(A_i\)\(A_i\leq 10^9\);第三行 \(N\) 個整數 \(B_i\)\(B_i\leq 10^9\)

輸出

輸出僅一行,包含 \(N\) 個整數,從小到大輸出這 \(N\) 個最小的和,相鄰數字之間用空格隔開。

資料範圍

對於 \(40\%\)的資料,滿足\(1\leq N \leq 500\)
對於 \(100\%\)的資料,滿足\(1\leq N \leq 100000\)

分析

如果這道題只要\(40\)分的話,其實二重迴圈列舉 \(A_i+B_i\) 即可。時間複雜度 \(O(n^2)\)

如果你想要AC的話...
我們不妨建立一個優先佇列,開始時將 \(A_i+B_1\) 放入優先佇列裡。如果此時的最小值為 \(A_x+B_1\),則取出 \(A_x+B_1\)並記錄,同時將 \(A_x+B_2\) 放入優先佇列中。對於整個過程,也是同理:取出優先佇列的最小值 \(A_x+B_y\)並記錄,再將 \(A_x+B_{y+1}\) 放入優先佇列中。重複 \(N\) 遍。時間複雜度 \(O(n\log n)\)

程式碼

#include<bits/stdc++.h>
#define N 100005
using namespace std;
using PII=pair<int,int>;
bool cmp(PII a,PII b){
	return a.first<b.first;
}
priority_queue<PII,vector<PII>,greater<PII> > q;
int n,a[N],b[N],cnt[N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		cnt[i]=1;
		scanf("%d",a+i);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",b+i);
	}
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++){
		q.push(make_pair(a[i]+b[1],i));
	}
	for(int i=1;i<=n;i++){
		PII tmp=q.top();
		q.pop();
		printf("%d ",tmp.first);
		q.push(make_pair(a[tmp.second]+b[++cnt[tmp.second]],tmp.second));
	}
	return 0;
}

T4 最小密度路徑

描述

給出了一張有 \(N\) 個點 \(M\) 條邊的加權有向無環圖,接下來有 \(Q\) 個詢問,每個詢問包括 \(2\) 個節點 \(X\)\(Y\),要求算出從 \(X\)\(Y\) 的一條路徑,使得密度最小(密度的定義為,路徑上邊的權值和除以邊的數量)。

輸入

第 $1 行包括2個整數 \(N\)\(M\)
第 $2 $ 到 \(M+1\) 行,每行三個數字 \(A,B,W\) ,表示從 \(A\)\(B\) 有一條權值為 \(W\)有向邊
\(M+2\) 行只有一個整數 \(Q\)
接下來 \(Q\) 行,每行有兩個正整數 \(X, Y\),表示一個詢問。

輸出

對於每個詢問輸出一行,表示該詢問的最小密度路徑的密度(保留 \(3\) 位小數)。
如果不存在從 \(X\)\(Y\) 的一條路徑,則輸出"OMG!"(不含引號)。

資料範圍

對於 \(50\%\) 的資料,有 \(1\leq N\leq 10, 1\leq M\leq 100, 1\leq W\leq 1000, 1\leq Q\leq 1000\)
對於 \(100\%\) 的資料,有 \(1\leq N\leq 50, 1\leq M\leq 1000, 1\leq W\leq 100000, 1\leq Q\leq 100000\)

分析

考場上時間不夠了。(主要是T2時間想的太久了)
所以寫了個dfs爆搜。(喜得54分)

程式碼

#include<bits/stdc++.h>
#define N 100005
#define M 55
using namespace std;
int n,m,Q;
const double INF=1145141919810.0;
struct EDGE{
	int to,nxt,v;
}e[N];
int head[N],cnt=0;
double dis[M][M];
void add(int u,int v,int w){
	e[++cnt].to=v;
	e[cnt].v=w;
	e[cnt].nxt=head[u]; 
	head[u]=cnt;
}
void dfs(int x,double sum,int edgeCnt,int origin)
{
	if(x!=origin)
		dis[origin][x]=min(dis[origin][x],sum/double(edgeCnt));
	for(int i=head[x];i;i=e[i].nxt){
		dfs(e[i].to,sum+double(e[i].v),edgeCnt+1,origin);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			dis[i][j]=INF;
		}
		dis[i][i]=0;
	}
	for(int i=1;i<=n;i++)
		dfs(i,0,0,i);
	scanf("%d",&Q);
	while(Q--){
		int x,y;
		scanf("%d%d",&x,&y);
		if(dis[x][y]==INF) 
			printf("OMG!\n");
		else 
			printf("%.3lf\n",dis[x][y]);
	}
	return 0;
}