AtCoder abc159 - AtCoder Beginner Contest 159


沒想到昨天的ABC竟是如此之水。而我卻沒打,真是太可惜了/kk

AtCoder比賽頁面傳送門

A - The Number of Even Pairs

AtCoder題目頁面傳送門

\(n+m\)個整數,其中\(n\)個是偶數,\(m\)個是奇數。求有多少種不同的選\(2\)個不同的數的方案,使得這\(2\)個數之和是偶數。

顯然,\(2\)個數之和是偶數當且僅當它們奇偶性相同。選\(2\)個不同的偶數有\(\dfrac{n(n-1)}2\)種方案,選\(2\)個不同的奇數有\(\dfrac{m(m-1)}2\)種方案,共\(\dfrac{n(n-1)}2+\dfrac{m(m-1)}2\)種方案。

下面是AC代碼:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,m;
	cin>>n>>m;
	cout<<n*(n-1)/2/*選2個不同的偶數*/+m*(m-1)/2/*選2個不同的奇數*/;
	return 0;
}

B - String Palindrome

AtCoder題目頁面傳送門

定義一個長度為奇數的字符串\(a\)是強回文的當且僅當\(a,a_{1\sim \frac{n-1}2},a_{\frac{n+3}2\sim |a|}\)\(3\)個字符串都是回文的。給定一個長度為奇數的字符串\(a\),判斷它是否強回文。

根據強回文的定義判斷即可。

下面是AC代碼:

#include<bits/stdc++.h>
using namespace std;
const int N=99;
int n;
char a[N+5];
bool pal(int l,int r){//[a[l~r]是回文的]
	while(l<=r){
		if(a[l]!=a[r])return false;
		l++;r--;
	}
	return true;
}
int main(){
	cin>>a+1;
	n=strlen(a+1);
	puts(pal(1,n)&&pal(1,n-1>>1)&&pal(n+3>>1,n)?"Yes":"No");//定義 
	return 0;
}

C - Maximum Volume

AtCoder題目頁面傳送門

給定一個整數\(x\)。求所有各棱長為實數且和為\(x\)的長方體中最大的體積是多少。

原問題即給定\(x\),求\(\max\limits_{a+b+c=x}\{abc\}\)。根據和定差小積大,當\(a=b=c=\dfrac x3\)時,\(abc\)最大為\(\left(\dfrac x3\right)^3\)

下面是AC代碼:

#include<bits/stdc++.h>
using namespace std;
int main(){
	double x;
	cin>>x;
	printf("%.100lf",pow(x/3,3));
	return 0;
}

D - Banned K

洛谷題目頁面傳送門 & AtCoder題目頁面傳送門

給定\(n\)個整數,第\(i\)個數為\(a_i\)。求\(\forall i\in[1,n]\),不能選\(a_i\)的情況下,有多少種不同的選\(2\)個相等的數的方案。

\(n\in\left[3,2\times10^5\right],a_i\in[1,n]\)

由於\(a_i\in[1,n]\),所以我們可以直接把數們放進桶里,\(buc_i=\sum\limits_{j=1}^n[a_j=i]\)。這樣,\(\forall i\in[1,n]\)\(i\)的答案顯然是\(\sum\limits_{j=1}^n\dfrac{(buc_j-[a_i=j])(buc_j-[a_i=j]-1)}2\)。但這樣時間復雜度顯然是\(\mathrm O\!\left(n^2\right)\)。於是考慮先算出不帶“不能選某數”的限制時的答案\(ans=\sum\limits_{i=1}^n\dfrac{buc_i(buc_i-1)}2\)\(\mathrm O(n)\),然后\(\forall i\in[1,n]\),從\(ans\)里減去必須選\(a_i\)時的方案數,答案為\(ans-(buc_{a_i}-1)\)。時間復雜度\(\mathrm O(n)\)

下面是AC代碼:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=200000;
int n;
int a[N+1];
int buc[N+1];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],buc[a[i]]++/*裝進桶*/;
	int ans=0;
	for(int i=1;i<=n;i++)ans+=buc[i]*(buc[i]-1)/2;//不帶限制時的答案 
	for(int i=1;i<=n;i++)cout<<ans-(buc[a[i]]-1)<<"\n";
	return 0;
}

E - Dividing Chocolate

洛谷題目頁面傳送門 & AtCoder題目頁面傳送門

給定一個\(n\times m\)的01字符矩陣\(a\)。求最少需要多少次水平或豎直切割,使得切出的每一塊都包含不超過\(s\)\(\texttt1\)

\(n\in[1,10],m\in[1,1000]\)

看到\(n\)如此之小,不難想到暴力枚舉水平切割的\(2^{n-1}\)種狀態。對於每一種狀態,從左往右貪心,每到一列就將水平切割切成的每一塊當前的\(\texttt1\)的個數增加若干,如果至少有\(1\)塊當前的\(\texttt1\)的個數超過了\(s\),便在本列與上一列之間豎直切割一刀,如果仍然超過,那么此種水平切割狀態就不能要。貪心的正確性顯然。對於每種水平切割狀態,如果能要的話更新答案即可。時間復雜度\(\mathrm O(2^nnm)\)

下面是AC代碼:

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int inf=0x3f3f3f3f;
const int N=10,M=1000;
int n,m,s;
char a[N+1][M+5];
int main(){
	cin>>n>>m>>s;
	for(int i=1;i<=n;i++)cin>>a[i]+1;
	int ans=inf;
	for(int i=0;i<1<<n-1;i++){//暴力枚舉2^(n-1)種水平切割狀態 
		vector<int> pos;//水平切割處 
		pos.pb(0);
		for(int j=1;j<n;j++)if(i&1<<j-1)pos.pb(j);
		pos.pb(n);
		int now=pos.size()-2;//此水平切割狀態當前切割次數 
		vector<int> cnt(pos.size()-1,0);//水平切割的每一塊當前'1'的個數 
		for(int j=1;j<=m;j++){//從左往右貪心 
			bool ok=true; 
			vector<int> cnt0(pos.size()-1,0);//本列水平切割的每一塊'1'的個數 
			for(int k=0;k<cnt.size();k++){//枚舉水平切割的每一塊 
				for(int o=pos[k]+1;o<=pos[k+1];o++)cnt0[k]+=a[o][j]^48;
				if(cnt0[k]>s)goto label_end;//不能要 
				if(cnt[k]+cnt0[k]>s)ok=false;//需要豎直切割 
			}
			if(ok)for(int k=0;k<cnt.size();k++)cnt[k]+=cnt0[k];//不豎直切割
			else now++,cnt=cnt0;//豎直切割 
		}
		ans=min(ans,now);//更新答案 
	label_end:;
	}
	cout<<ans;
	return 0;
}

F - Knapsack for All Segments

洛谷題目頁面傳送門 & AtCoder題目頁面傳送門

給定\(n\)個整數,第\(i\)個數為\(a_i\)。定義\(f(l,r)\)表示\([l,r]\)內和為\(m\)的集合的數量。求\(\sum\limits_{i=1}^n\sum\limits_{j=i}^nf(i,j)\)。答案對\(998244353\)取模。

\(n,m,a_i\in[1,3000]\)

考慮算貢獻法。對於某個集合\(\{a_{x_i}\mid i\in[1,k]\}\),其中\(x_1<x_2<\cdots<x_k\),區間\([l,r]\)包含它當且僅當\(l\in[1,x_1],r\in[x_k,n]\),所以此集合對答案的貢獻為\(x_1(n-x_k+1)\)

看到“和為某數”一類的問題,不難想到類似背包的值域DP。設\(dp_{i,j}\)表示考慮到第\(i\)個數,所有和為\(j\)的集合最左邊的位置之和。邊界是\(dp_{0,i}=0\),目標是\(\sum\limits_{i=1}^n\begin{cases}0&m<a_i\\i(n-i+1)&m=a_i\\dp_{i-1,m-a_i}(n-i+1)&m>a_i\end{cases}\)(枚舉和為\(m\)的集合最右邊的位置貢獻答案),狀態轉移方程是\(dp_{i,j}=dp_{i-1,j}+[j=a_i]i+\begin{cases}dp_{i-1,j-a_i}&j\geq a_i\\0&j<a_i\end{cases}\)。時間復雜度\(\mathrm O(nm)\)

另外,此狀態轉移方程與01背包極其相似,可以用類似01背包的方式將DP數組壓成一維,並在每次用\(a_i\)更新\(dp'\)之前及時將\(i\)作為和為\(m\)的集合最右邊的位置貢獻答案。

下面是AC代碼:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=3000,M=3000;
int n,m;
int a[N+1];
int dp[M+1];//dp' 
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	int ans=0;
	for(int i=1;i<=n;i++){
		if(m==a[i])(ans+=1ll*i*(n-i+1)%mod)%=mod;
		else if(m>a[i])(ans+=1ll*dp[m-a[i]]*(n-i+1)%mod)%=mod;//更新答案 
		for(int j=m;j>=a[i];j--)(dp[j]+=dp[j-a[i]]+(j==a[i]?i:0))%=mod;//轉移 
	}
	cout<<ans;
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM