100+35+10+10 拿下 rk7,拓撲AC的A題也太過困難了吧……
T1
題意
給定陣列 \(a\),陣列長度為 \(n\)。
定義 \(f(x)\) 表示有多少對 \((i,j)\) 滿足 \((a_i+x)\) 是 \((a_j+x)\) 的子集。
給定 \(k\),保證 \(a_i<2^k\),求 \(\sum_{i=0}^{2^{k-1}}f(i)\)。
\(n\leq20000,k\leq 60\)。
賽時思路
想對這個玩意進行轉化。
首先轉化成了位運算形式,即 \((a_i+x)|(a_j+x)==a_j+x\)。
然後就可以列舉 \(i,j,k\) 了,能拿 \(15pts\)。
現在又兩條路走:要麼對於每個 \(f(x)\) 單獨考慮,要麼考慮每對 \((i,j)\) 對答案的貢獻。
一般這種題第二條路都更像是正解。
然後要進行進一步轉化,我就不會了。
急急急!打完 \(O(n^2 2^k)\) 後罰坐至比賽開始兩個多小時。
看看每兩個數之間的貢獻是否有什麼規律吧。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1000;
int n,k,num[N][N],a[N],ans;
signed main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
ios::sync_with_stdio(0);
k=3;
n=1<<k;
for(int i=1;i<=n;i++){
a[i]=i-1;
}
for(int i=0;i<(1<<k);i++){
for(int j=1;j<=n;j++){
for(int l=1;l<=n;l++){
if(((a[j]+i)|(a[l]+i))==a[l]+i){
num[j][l]++;
ans++;
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<num[i][j]<<' ';
}
cout<<'\n';
}
cout<<ans<<'\n';
}
先打出來 \(k=3\) 時每個數對的貢獻看看。
輸出:
8 4 4 2 4 2 2 1
0 8 4 4 2 4 2 2
0 0 8 4 4 2 4 2
0 0 0 8 4 4 2 4
0 0 0 0 8 4 4 2
0 0 0 0 0 8 4 4
0 0 0 0 0 0 8 4
0 0 0 0 0 0 0 8
嗯?怎麼都是 \(2\) 的冪次?
再大一點呢?
16 8 8 4 8 4 4 2 8 4 4 2 4 2 2 1
0 16 8 8 4 8 4 4 2 8 4 4 2 4 2 2
0 0 16 8 8 4 8 4 4 2 8 4 4 2 4 2
0 0 0 16 8 8 4 8 4 4 2 8 4 4 2 4
0 0 0 0 16 8 8 4 8 4 4 2 8 4 4 2
0 0 0 0 0 16 8 8 4 8 4 4 2 8 4 4
0 0 0 0 0 0 16 8 8 4 8 4 4 2 8 4
0 0 0 0 0 0 0 16 8 8 4 8 4 4 2 8
0 0 0 0 0 0 0 0 16 8 8 4 8 4 4 2
0 0 0 0 0 0 0 0 0 16 8 8 4 8 4 4
0 0 0 0 0 0 0 0 0 0 16 8 8 4 8 4
0 0 0 0 0 0 0 0 0 0 0 16 8 8 4 8
0 0 0 0 0 0 0 0 0 0 0 0 16 8 8 4
0 0 0 0 0 0 0 0 0 0 0 0 0 16 8 8
0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 8
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 16
還是對的!並且對角線上數一樣!
這就意味著我們只需要得到第一行就可以算任意位置的貢獻了!
對第一行取個 \(\log\) 看看呢?
k=4: 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0
k=5: 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0
k=6: 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0
k=7: 7 6 6 5 6 5 5 4 6 5 5 4 5 4 4 3 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0
好像非常有規律啊。
首先 \(k-1\) 的表是 \(k\) 的表的字尾。
然後從後往前4位4位地考慮,發現每4位都是形如:\(a+2,a+1,a+1,a\) 的。
從 \(2 1 1 0\) 開始,每次變換就是把每個數當成 \(a\) 後變成四個數。
答案就是變換的字尾。
但是這樣都能考慮了,為什麼不2位2位考慮呢?
然後再看了一會,發現這玩意其實就是相應下標的 \(popcount\)!
\(popcount(0)=0\)
\(popcount(1)=1\)
\(popcount(2)=1\)
\(popcount(3)=2\)
……
然後就會了,\(O(n^2)\) 列舉後把規律糊上去就行了。
後來又被 1ll<<k 和 __builtin_popcountll 硬控了半個小時。
#include<bits/stdc++.h>
#define W "w"
#define int long long
using namespace std;
const int N=2e5+10,mod=998244353;
int n,k,a[N],ans,pw[100];
signed main(){
freopen("subset.in","r",stdin);
freopen("subset.out",W,stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
pw[0]=1;
for(int i=1;i<=k;i++) pw[i]=pw[i-1]*2%mod;
for(int i=1;i<=n;i++) cin>>a[i];
int all=(1ll<<k);
for(int i=1;i<=n;i++) a[i]++;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i]>a[j]) continue;
int now=all-a[j]+a[i]-1;
(ans+=pw[__builtin_popcountll(now)])%=mod;
}
}
cout<<ans<<'\n';
}
T2
題意
給定陣列 \(a\),長度為 \(n\)。
將 \(a_i\) 變成 \(x\) 需要付出 \(|a_i-x|\) 的代價。
求將 \(a\) 中每個數都變成 \(d\) 的冪次的最小代價。
\(d\) 可任選。
設 \(V=max(a_i)\)
\(n\leq 100000,1\leq V\leq 10^{12}\)
賽時思路
如果 \(d\) 固定的話,我會 \(O(n)\)。
所以問題在於確定 \(d\)。
二分?這玩意好像沒啥單調性。
根號分治!
對於 \(d\leq\sqrt V\) ,可以列舉 \(d\),能做到 \(O(n\sqrt V)\)。
(實際上可以更優,但是我場上沒想到,所以一直覺得根號分治不是正解。
對於 \(d>\sqrt V\),我不會!
於是將 \(d\) 列舉到 600 後跑路,拿了 \(35pts\)。
T3
打了 \(10pts\) 爆搜。
T4
輸出了 \(0.5000000000\),獲得 \(10pts\)。
正解會補的(大概會?)