Codeforces Round 940 (Div. 2) and CodeCraft-23

potential-star發表於2024-04-23

Codeforces Round 940 (Div. 2) and CodeCraft-23

前四題難度適中,總體還算不錯,我想想都能做。E題考察威爾遜和質數篩字首和算貢獻。F題是資料結構,據說很版,還沒補。

A題:題意:給出n個木棍,最多組成多少個多邊形

Solution:統計各長度木棍的數量,全部貪心成三角形

void solve(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	map<int,int>mp;
	for(int i=1;i<=n;i++)mp[a[i]]++;
	int ans=0;
	for(auto [x,y]:mp)ans+=y/3;
	cout<<ans<<endl;
}

B:題意:給出n和k,要求把k拆成n個非負整數,希望最大化n個數的或和的二進位制的1的數量

Solution:對於n=1進行特判,無法拆分。從一般情況考慮我們只需要找到k的最高的二進位制的位的數tmp=(1<<__lg(k))-1,和k-tmp,剩下的數全部為0.考慮這種情況的代價是把最高位的1消耗去獲得低位所有的1.

上述貪心顯然存在反例就是舒適就是二進位制全1的,那麼我們考慮特判這類數即可。程式碼實現過程中直接對兩種構造的答案取max,誰大就用誰的構造方案

int cal(int x){
	int res=__builtin_popcount(x);
	return res;
}
void solve(){
	int k;cin>>n>>k;
	if(n==1){
		cout<<k<<endl;
		return ;
	}
	int len=__lg(k);
	int ans=cal(k);
	int tmp=(1<<len)-1;
	if(ans>=cal(tmp)){
		cout<<k<<" ";
		for(int i=1;i<=n-1;i++)cout<<0<<" ";
	}
	else {
		cout<<tmp<<" "<<k-tmp<<" ";
		for(int i=1;i<=n-2;i++)cout<<0<<" ";
	}
	cout<<endl;
}

C:題意:人和電腦在正方形網格圖裡下國際象棋。人每次放白車,人放(x,y),電腦放黑車(y,x).如果人放對角線,電腦不回應,人繼續先手。一個車所在一行一列不能出現第二個車。

現在棋盤大小,給出前k步棋譜,求最終局面有多少種局面

Solution:考慮給出遞推的證明。首先注意到問題只和當前還有幾行幾列空閒有關,已經被佔領的行無法對方案做出貢獻。考慮現在填一個n行n列的空白棋盤。由於順序與最終方案無關,所以根據任意性,我們假設考慮第一步就在第n行落子,也就是說第n行的落子可能是在第j步,當前欽定第一步放,就去除了重複局面 對計算造成的影響。

\(dp[n]=dp[n-1]+2*(n-1)*dp[n-2]\).含義是如果落子在第n行的對角線則問題劃歸到n-1規模,如果落子在第n行非對角線,則電腦對應位置落黑車,會減少兩行兩列,問題劃歸到n-2規模.考慮可以在第n列對稱,方案數直接*2.

int dp[N];
 
 
void solve(){
	//cout<<dp[3]<<endl;
	int k;
	cin>>n>>k;
	for(int i=1;i<=k;i++){
		int x,y;cin>>x>>y;
		if(x==y)n-=1;
		else n-=2;
	}
	
	cout<<dp[n]<<endl;
}

D題意:\(\left(\bigoplus_{x\le i\le z} a_i \right)\oplus a_y > \left(\bigoplus_{x\le i\le z} a_i \right)\),求滿足條件的x,y,z有多少對,其中\(x<=y<=z\)

首先套路的,對於區間異或和提前預處理字首異或和,考慮不等式成立條件,從\(a_{y}\)的二進位制最高位考慮,當對應區間異或和該位為0的時候不等式成立。為0的條件的區間異或和兩個端點需要同時為1或者為0.

所以我們可以預處理拆位的字首和,字尾和,每次統計的是1的數量,0的數量作為補集也很好得到。

onst int len=__lg((ull)2e9);
bitset<5>b;
//a_{y}^t>t  考慮a_{y}的最高位,如果t這位為0,那麼不等式成立。
//如果t這位為1,不等式一定不成立
//考慮列舉y,然後看他左邊和右邊的這一位的字首和有多少異或結果為0
//拆位字首和,字尾和,快速統計數量
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	vector<int>s(n+1,0);
	for(int i=1;i<=n;i++)s[i]=s[i-1]^a[i];
vector<vector<int>>pre(n+2,vector<int>(len+2,0));
vector<vector<int>>suf(n+2,vector<int>(len+2,0));
	for(int i=1;i<=n;i++){
		for(int j=len;j>=0;j--){
			int u=(s[i]>>j)&1;
			if(u==1)pre[i][j]=1;
			}
	}
	for(int i=n;i>=1;i--){
		for(int j=0;j<=len;j++){
			suf[i][j]=suf[i+1][j]+pre[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<=len;j++){
			pre[i][j]+=pre[i-1][j];
		}
	}
	int ans=0;
	// for(int i=1;i<=n;i++){
		// b=s[i];
		// cerr<<b<<endl;
	// }
	for(int i=1;i<=n;i++){
		
		int u=__lg(a[i]);
		int c1=pre[i-1][u];
		int c2=suf[i][u];
	//	cerr<<u<<" "<<c1<<" "<<c2<<endl;
		ans+=c1*c2;
		//考慮這裡0也可以作為本位為0貢獻
		ans+=(max(i-c1,0LL))*(max(n-i+1-c2,0LL));
	}
	cout<<ans<<endl;
}

E:定義一種F運算,求\(\sum\limits_{i=1}^n \sum\limits_{j=1}^i \left( F(i,j) \bmod j \right).\)給出\(F(i,j)\)的定義是從i個元素中選j個元素進行圓排列的方案數。顯然\(F(i,j)=C(i,j)*(j-1)!\)

Solution:我們需要進一步變形上面式子\(F(i,j) \bmod j = \frac{i(i-1)\cdots(i-j+1)}{j} \bmod j = \left( (j-1)! \times \left\lfloor \frac{i}{j} \right\rfloor \right) \bmod j\)

考慮對將組合數拆成階乘,然後發現分子連續j項一定構成modj的剩餘系,所以可以進一步化簡成下取整形式。

  • 考慮威爾遜定理,對於所有質數\((j-1)! \equiv -1 \bmod j\)
  • 對於所有大於4的合數,\((j-1)! \equiv 0 \bmod j\)

因此我們需要計算對於固定質數j,不同的i對其的貢獻,這裡就是交換了求和次序。

對於固定j,不同的i造成的貢獻如何快速計算?

一般地,對於i從kp到k(p+1)-1,他們的貢獻是相同的,我們希望這段i每個數都加上-k modj的貢獻,靜態區間加我們使用差分維護貢獻。最後我們求一遍字首和得到每個i的貢獻,再求一遍得到前n個數的貢獻,也就是答案了

我們還需要對4進行單獨計算,作為特例。

int minp[N];
int prime[N];
int cnt=0;
int ans[N];
int g[N];
//打表固然美麗,但數學推導更為重要,推出數學式子才理解他們說的打表規律的本質
//題解寫的很不錯
//總的來說就是先推數學式子用威爾訓化解,對於4特判。
//對於所有質數算貢獻,交換求和次序,統計答案差分後,字首和求出所有答案
void init(){
	for (int i = 2; i < N; i++)minp[i] = i;//透過這個初始化省下bool陣列空間
        int cnt = 0;
     for (int i = 2; i < N; i++) {
    if (minp[i] == i) prime[cnt++] = i;
    for (int j = 0; j < cnt && prime[j] * i < N; j++) {
        minp[prime[j] * i] = prime[j];
        if (i % prime[j] == 0) break;
        }
}
      for(int i=0;i<cnt;i++){
	       int p=prime[i];
	   //    if(p<300)cerr<<p<<endl;
	       for(int j=p;j<=1000000;j+=p){
	     int u=-j/p;u=(u%p+p)%p;
	       // int u = (p - ((j / p) % p)) % p;
	       //	if(j<100)cerr<<p<<" "<<u<<endl;
	       g[j]+=u;g[j]%=mod;
	      if(j+p<N){g[j+p]-=u;g[j+p]+=mod;g[j+p]%=mod;}
	       }
  }
           int p=4;
            for (int j=4;j<=1000000;j+=4){
      	     
	       	 int u=2*j/p;u%=p;
	          g[j]+=u;g[j]%=mod;
	         if(j+p<N){g[j+p]-=u;g[j+p]+=mod;g[j+p]%=mod;}
	       }
	       
	       
	       for(int i=1;i<=1000000;i++){
	       	g[i]+=g[i-1];g[i]%=mod;
	       	ans[i]=ans[i-1]+g[i];ans[i]%=mod;
	       }
	       
}

相關文章