【A~E】AtCoder Beginner Contest 365

SnapYust發表於2024-08-03

A - Leap Year

題目大意

給定 \(n\),求第 \(n\) 年的天數(\(365\)\(366\))。

思路

顯然地,我們需要判斷這個是否為閏年。

如果 \(n\) 不能被 \(4\) 整除,那麼不是閏年。

如果 \(n\) 可以被 \(400\) 整除,那麼是閏年。

如果 \(n\) 不可以被 \(100\) 整除但是可以被 \(4\) 整除,那麼是閏年。

否則就不是閏年。

很簡單。

程式碼

#include<bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int n;cin>>n;
	
	if(n%4!=0){
		cout<<365;
	}
	else if(n%400==0){
		cout<<366;
	}
	else if((n%100!=0)&&n%4==0){
		cout<<366;
	}
	else cout<<365<<endl;
	
	return 0;
}

B - Second Best

題目大意

給定 \(\{a_n\}\),求次大值的位置。

思路

打擂。記目前最大值為 \(max\),目前次大值為 \(cmax\)

如果 \(a_i>max\),那麼把 \(cmax\) 更新為 \(max\),把 \(max\) 更新為 \(a_i\),同時記錄位置。

如果 \(a_i<max\) 並且 \(a_i>cmax\),那麼把 \(cmax\) 更新為 \(a_i\),同時記錄位置。

程式碼

#include<bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int n;cin>>n;
	vector<int>a(n+1);
	
	for(int i=1;i<=n;i++)
		cin>>a[i];
	
	int ans1=a[1],ans2=0;
	int pos1=1,pos2=0;
		
	for(int i=2;i<=n;i++){
		if(a[i]>ans1){
			ans2=ans1;
			pos2=pos1;
			ans1=a[i];
			pos1=i;
		}
		else if(a[i]>ans2){
			ans2=a[i];
			pos2=i;
		}
	}
	
	cout<<pos2<<endl;
	
	return 0;
}

C - Transportation Expenses

題目大意

給定 \(\{A_N\}\),要求 \(\sum\min\{A_i,x\}\le M\),求最大值 \(x\)(可能為無窮大)。

思路

很板的二分答案。

首先考慮無窮大的答案。可以先計算序列的總和和 \(M\) 比較,如果這個都小於等於 \(M\),那麼肯定答案是無窮大。

如果不是無窮大呢?可以試著列舉一個 \(x\),然後對於每一個 \(x\) 判斷是否滿足題目條件,然後記錄最大的 \(x\),時間複雜度 \(O(NM)\)

考慮二分一個 \(x\) 然後來判斷,題目要求 \(x\) 的最大值,可以發現若此時的 \(x\) 為最大,那麼對於每一個 \(k\le x\) 都可以滿足題目條件,而對於每一個 \(k\ge x\) 都不能滿足題目條件,滿足單調性,寫二分答案,時間複雜度 \(O(N\log M)\)

程式碼

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,m,a[200005],ans=-1e17;

bool check(int x){
	int res=0;
	for(int i=1;i<=n;i++)
		res+=min(a[i],x);
	return (res<=m);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i];
		
	if(check(1e17)){
		cout<<"infinite"<<endl;
		return 0;
	}
	
	int l=0,r=m;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid))ans=mid,l=mid+1;
		else r=mid-1;
	}

    cout<<ans<<endl;
	
	return 0;
}

D - AtCoder Janken 3

題目大意

兩個人玩石頭剪刀布,已知 \(A\) 的出招順序,而且 \(B\) 從來沒有輸給 \(A\),而且 \(B\) 相鄰兩次的出招是不一樣的(比如這次 \(B\) 出了石頭,那麼他下一次就不能出石頭),求 \(B\) 最多可以贏多少局。

思路

因為發現 \(B\) 出招的限制條件只和前一次和後一次的有關,所以這個問題顯然滿足無後效性,考慮 DP。

\(f_{i,0/1}\) 表示已經過了 \(i\) 局並且第 \(i\)\(B\)\(A\) 平局或贏(取 \(0\) 時兩人平局,取 \(1\)\(B\) 贏)時的最大勝場數。

顯然發現透過判斷當前局和上一局的平局和勝利的出招是否相同來進行轉移。

具體見程式碼。

程式碼

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,f[200001][2]={0};
string s;
char lp,lwin;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>s;
	
	f[0][0]=0,f[0][1]=1;
	if(s[0]=='S')lwin='R',lp='S';
	else if(s[0]=='R')lwin='P',lp='R';
	else lwin='S',lp='P';
	
	for(int i=1;i<n;i++){
		char p,win;
		if(s[i]=='S')win='R',p='S';
		else if(s[i]=='R')win='P',p='R';
		else win='S',p='P';
		
		if(p!=lp)f[i][0]=max(f[i-1][0],f[i][0]);
		if(p!=lwin)f[i][0]=max(f[i-1][1],f[i][0]);
		
		if(win!=lp)f[i][1]=max(f[i-1][0]+1,f[i][1]);
		if(win!=lwin)f[i][1]=max(f[i-1][1]+1,f[i][1]);
		
		lp=p,lwin=win;
	}
	
	cout<<max(f[n-1][0],f[n-1][1])<<endl;
	
	return 0;
}

E - Xor Sigma Problem

題目大意

給定 \(\{A_N\}\),求 \(\begin{aligned}\sum_{i=1}^{N-1}\sum_{j=i+1}^NA_i\oplus A_{i+1}\oplus\cdots\oplus A_j\end{aligned}\)

思路

考慮一下這道題的弱化版:求序列中兩兩異或的總和,也就是求 \(\begin{aligned}\sum_{i=1}^{N-1}\sum_{j=i+1}^NA_i\oplus A_j\end{aligned}\)

不難發現,正常求是需要 \(O(N^2)\) 的複雜度,考慮拆位。

也就是把每一個數都拆成二進位制位,比如對於 \(A=\{2,3,5,8\}\),那麼拆位之後就是:

\[2 : 0\ 0\ 1\ 0 \]

\[3 : 0\ 0\ 1\ 1 \]

\[5 : 0\ 1\ 0\ 1 \]

\[8 : 1\ 0\ 0\ 0 \]

可以發現,每一位的貢獻其實都是這一位上兩兩異或為 \(1\) 的貢獻。

也就是說,第一位會產生 \(3\) 的貢獻(\(3\)\(0\)\(1\)\(1\))。

以此類推,每一位的貢獻分別為 \(3\)\(3\)\(4\)\(4\)

那麼乘上每一位的權重,可以得出答案為:

\[3\times2^3+3\times2^2+4\times2^1+4\times2^0=48 \]

再來考慮一下原問題,不難發現我們可以預處理出一個異或字首 \(s\),也就是 \(s_i=s_{i-1}\oplus A_i\),那麼類似字首和,根據異或的自反性質,區間 \([l,r]\) 的異或為 \(s_r\oplus s_{l-1}\)

也就是說,我們目前得到了所有的區間的異或值,不難看出原題的答案就是:

\[\begin{aligned}\sum^{N}_{i=1}s_i+\sum^{N}_{i=2}{s_i\oplus s_1}+\cdots+\sum^{N}_{i=N-1}{s_i\oplus s_{N-2}}-\sum^N_{i=1}A_i\end{aligned} \]

這個式子看起來挺複雜吧?

其實再深入思考一下就可以發現其實中間的那一大串(\(\begin{aligned}\sum^{N}_{i=2}{s_i\oplus s_1}+\cdots+\sum^{N}_{i=N-1}{s_i\oplus s_{N-2}}\end{aligned}\))拆開來看,其實就是 \(s_1\)\(s_N\) 的兩兩異或!

也就是說,我們可以根據 \(s_i\) 來重新建一個序列,求完了這個序列的兩兩異或和之後再加上 \(\sum s_i\),然後因為原題中沒有包含 \(A_i\) 的情況,所以還要再減去 \(\sum A_i\)

具體見程式碼。

程式碼

#include<bits/stdc++.h>
#define int long long
using namespace std;

int ans[20]={0};
int b[200001];
int sum[200001]={0};
long long res=0;
int n,a,i,len,mx=-1;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
    cin>>n;
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]^b[i];
    
    for(i=1;i<=n;i++){
        a=sum[i];
        len=0;
        while(a){
            len++;
            if(a&1)ans[len]++;
            a=a>>1;
        }
        mx=max(mx,len);
    }
    
    for(i=1;i<=mx;i++)
        res+=(1ll<<(i-1))*ans[i]*(n-ans[i]);
    
    for(int i=1;i<=n;i++)
    	res+=sum[i],res-=b[i];
    
    cout<<res;
    
    return 0;
}

相關文章