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;
}
}