sb atcoder 提前比賽時間/fn/fn/fn……sb atcoder 還我 rating/zk/zk/zk
A
簽到題,枚舉位數 \(+\) 前導 \(1\) 個數然后隨便算算貢獻即可,時間復雜度 \(\log^2_{10}(n)\)
B
u1s1 這個 B 感覺比一般的 B 難不少啊……
考慮貪心。首先第一位我們隨便欽定 \(n\) 個 \(0\),\(n\) 個 \(1\) 和 \(n\) 個 \(2\),那么后面的位我們肯定會貪心地讓第一位填 \(2\) 的都填 \(0\),第一位填 \(0/1\) 的自然不用管它們因為我們后面自然有辦法為它們填數,但是這樣會導致字符串出現重復的情況,怎么解決呢?如果我們發現目前不能區分出來的字符串個數 \(>3^{l-i}\),其中 \(i\) 表示當前填到了第幾位,那么我們就貪心地依次填上 \(0000...0,000...1,\cdots,1111...11\),然后把最后的零頭放到那“不能被區分出來的字符串集合”中即可。
可能講得有點玄乎,看代碼可能會比較好理解。
const int MAXN=1.5e5;
int n,l,pw3[18];
string s[MAXN+5];
int main(){
scanf("%d%d",&n,&l);
for(int i=(pw3[0]=1);i<=l;i++) pw3[i]=pw3[i-1]*3;
vector<int> id;
for(int i=1;i<=n;i++) id.pb(i);int cur=n;
for(int i=1;i<=n;i++) s[i].pb('0'),s[i+n].pb('1'),s[i+n+n].pb('2');
for(int _=2;_<=l;_++){
cur=id.size();
if(cur<=pw3[l-_]){
for(int x:id) s[x].pb('2'),s[x+n].pb('1'),s[x+n+n].pb('0');
} else if(cur<=pw3[l-_]*2) {
for(int i=0;i<pw3[l-_];i++){
int x=id[i];
// printf("%d\n",x);
s[x].pb('2');s[x+n].pb('1');s[x+n+n].pb('0');
for(int j=0;j<l-_;j++){
s[x].pb('0'+(i/pw3[j]%3));
s[x+n].pb('0'+(i/pw3[j]%3));
s[x+n+n].pb('0'+(i/pw3[j]%3));
}
} vector<int> nw;
for(int i=pw3[l-_];i<id.size();i++){
int x=id[i];
// printf("%d\n",x);
s[x].pb('0');s[x+n].pb('2');s[x+n+n].pb('1');
nw.pb(x);
} id=nw;
} else {
for(int i=0;i<pw3[l-_];i++){
int x=id[i];
s[x].pb('2');s[x+n].pb('1');s[x+n+n].pb('0');
for(int j=0;j<l-_;j++){
s[x].pb('0'+(i/pw3[j]%3));
s[x+n].pb('0'+(i/pw3[j]%3));
s[x+n+n].pb('0'+(i/pw3[j]%3));
}
}
for(int i=pw3[l-_];i<pw3[l-_]*2;i++){
int x=id[i];
s[x].pb('0');s[x+n].pb('2');s[x+n+n].pb('1');
for(int j=0;j<l-_;j++){
s[x].pb('0'+(i/pw3[j]%3));
s[x+n].pb('0'+(i/pw3[j]%3));
s[x+n+n].pb('0'+(i/pw3[j]%3));
}
} vector<int> nw;
for(int i=pw3[l-_]*2;i<id.size();i++){
int x=id[i];
s[x].pb('1');s[x+n].pb('0');s[x+n+n].pb('2');
nw.pb(x);
} id=nw;
}
}
for(int i=1;i<=n*3;i++) cout<<s[i]<<endl;
return 0;
}
C
請問您見過幾個 arc C 被評到 2000+
首先考慮一個非常朴素的做法,我們先事先將 \(X\) 減 \(1\),然后每次從高位到低位一位一位考慮,如果當前 \(X\) 等於零那就直接 break
,否則如果當前 \(X\le 2^{n-l}\),其中 \(l\) 為當前考慮的位,那么我們就在這一位上填 \(0\) 並將 \(X\) 減一,否則我們就在這一位上填 \(1\) 並將 \(X\) 減去 \(2^{n-l}\)
然后就是我降智的地方了。由於這題 \(X\) 很大需要高精度,因此我以為直接減 \(1\) 復雜度會退化到平方,於是想着打各種標記之類的奇怪玩意兒,WA 了好幾發,事實上直接減 \(1\) 復雜度是正確的,感性理解一下不難發現當你進行一次減 \(1\) 之后退了很多很多位,那么在接下來很長一段時間內就不可能再退這么多位,如果理性證明地大概就 \(\sum\limits_{i=0}^{\log_2(n)}\lfloor\dfrac{n}{2^i}\rfloor=\mathcal O(n)\),因此總時間復雜度也是 \(\mathcal O(n)\)
D
考慮 \(c_i=a_i\oplus b_i\),那么不難發現對於一組 \(i,j\),\(a_i\oplus a_j\) 與 \(b_i\oplus b_j\) 從高到低第一個值不同的位就是 \(c_i\oplus c_j\) 二進制下的 significant bit
,下文中稱其為第 \(j\) 位,因此 \(\min(a_i\oplus a_j,b_i\oplus b_j)\) 取到 \(a_i\oplus a_j\) 當且僅當 \(a_i\oplus a_j\) 第 \(d\) 位為 \(1\)。
我們再考慮這樣一個問題,給定序列 \(a\) 求它們兩兩異或和的和,01-trie 是一種方法,事實上我賽時一開始也考慮了這個做法,但是有一種更簡單的做法是你考慮每一位的貢獻,我們對於每一位記錄一下有多少個數這一位為 \(0\),有多少個數這一位為 \(1\),這樣可以 \(\mathcal O(1)\) 計算每一位的貢獻。
接下來考慮原問題。我們將所有 \(c_i\) 插入 01-trie,然后對於 \(c_i\) 每一個為 \(1\) 的位 \(d\),我們在其對應的 01-trie 的節點上開 4 個長度為 \(\log(n)\) 的數組,下標為 \(j\) 的位置分別維護這樣幾個信息:對於經過這個節點且 \(c_i\) 為 \(1\) 的 \(i\),有多少個:
- \(a_i\) 第 \(d\) 位為 \(0\) 的 \(i\),滿足 \(a_i\) 的第 \(j\) 位為 0/1
- \(a_i\) 第 \(d\) 位為 \(0\) 的 \(i\),滿足 \(b_i\) 的第 \(j\) 位為 0/1
- \(a_i\) 第 \(d\) 位為 \(1\) 的 \(i\),滿足 \(a_i\) 的第 \(j\) 位為 0/1
- \(a_i\) 第 \(d\) 位為 \(1\) 的 \(i\),滿足 \(b_i\) 的第 \(j\) 位為 0/1
這樣在遍歷 01-trie 計算貢獻的過程中,我們考慮計算一個 \(i\) 對答案的貢獻時,可以考慮 \(c_i\) 所有為 \(0\) 的位,然后通過預處理的信息在單次 \(\mathcal O(\log n)\) 的時間內計算貢獻。
記得加上 \(c_i=c_j\) 的貢獻,這一部分的計算是 trivial 的,直接對每一種 \(c_i\) 重復一遍上面弱化版的過程即可。
時間復雜度 \(n\log^2n\)。
const int MAXN=262144;
const int LOG_N=18;
const int MAXP=2097152;
int n,A[MAXN+5],B[MAXN+5],C[MAXN+5];
int ch[MAXP+5][2],cnt[4][MAXP+5][LOG_N+2][2],rt=0,ncnt=0;
vector<int> pc[MAXN+5];
ll res=0;
void insert(int &k,int v,int dep,int a,int b){
if(!k) k=++ncnt;if(!~dep) return;
insert(ch[k][v>>dep&1],v,dep-1,a,b);
if(v>>dep&1){
if(a>>dep&1){
for(int j=0;j<LOG_N;j++) cnt[0][ch[k][v>>dep&1]][j][a>>j&1]++;
for(int j=0;j<LOG_N;j++) cnt[1][ch[k][v>>dep&1]][j][b>>j&1]++;
} else {
for(int j=0;j<LOG_N;j++) cnt[2][ch[k][v>>dep&1]][j][a>>j&1]++;
for(int j=0;j<LOG_N;j++) cnt[3][ch[k][v>>dep&1]][j][b>>j&1]++;
}
}
}
void query(int k,int v,int dep,int a,int b){
if(!k||!~dep) return;
query(ch[k][v>>dep&1],v,dep-1,a,b);
if(~v>>dep&1){
int c=ch[k][~v>>dep&1];
if(!c) return;
// printf("%d %d %d %d %d\n",v,dep,c,a,b);
if(a>>dep&1){
for(int j=0;j<LOG_N;j++) res+=1ll*cnt[0][c][j][~a>>j&1]*(1<<j);
for(int j=0;j<LOG_N;j++) res+=1ll*cnt[3][c][j][~b>>j&1]*(1<<j);
} else {
for(int j=0;j<LOG_N;j++) res+=1ll*cnt[1][c][j][~b>>j&1]*(1<<j);
for(int j=0;j<LOG_N;j++) res+=1ll*cnt[2][c][j][~a>>j&1]*(1<<j);
}
}
}
int cnt0[MAXN+5][LOG_N+2][2];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&A[i]);
for(int i=1;i<=n;i++) scanf("%d",&B[i]),C[i]=A[i]^B[i],pc[C[i]].pb(i);
for(int i=1;i<=n;i++) insert(rt,C[i],LOG_N-1,A[i],B[i]);
for(int i=1;i<=n;i++) for(int j=0;j<LOG_N;j++)
cnt0[C[i]][j][A[i]>>j&1]++;
// for(int i=1;i<=n;i++) printf("%d\n",c[i]);
for(int i=0;i<MAXN;i++){
ll s=0;
for(int id:pc[i]) for(int j=0;j<LOG_N;j++)
s+=1ll*cnt0[i][j][~A[id]>>j&1]*(1<<j);
s>>=1;res+=s;
}
for(int i=1;i<=n;i++) query(rt,C[i],LOG_N-1,A[i],B[i]);
printf("%lld\n",res);
return 0;
}
E
並沒有自己想出來,搬運官方題解 ing
首先考慮如果我們想要盡量讓大的數都在最終的 \(s\) 中那該怎么選擇每次插入的數。顯然按 \(1,2,3,\cdots,A\) 的順序插入肯定是最優的。因此我們假設 \(b_1,b_2,\cdots,b_{A-B}\) 為按 \(1,2,3,\cdots,A\) 順序插入后剩余的數組成的集合,其中 \(b_i<b_{i+1}\)。
我們再假設 \(c_1,c_2,\cdots,c_{A-B}(c_i<c_{i+1})\) 為按某種順序插入后剩余的數組成的集合,那么我們不妨猜測 \(c_i\le b_i(i\in[1,A-B])\)。我們假設 \(p_1,p_2,\cdots,p_{B}\) 為按 \(1,2,3,\cdots,A\) 中不在剩余集合中的數組成的集合,\(q_1,q_2,\cdots,q_B\) 為按 \(c_1,c_2,\cdots,c_{A-B}\) 對應的順序插入后不在剩余集合中的數組成的集合,其中 \(p_i<p_{i+1},q_i<q_{i+1}\),然后對於第 \(i\) 次插入考慮這樣構造:
- 如果 \(\exists j,s.t.b_j=i\),那么我們此次插入中插入 \(c_j\),其中 \(b_j=i\)。
- 否則必然 \(\exists j,s.t.p_j=i\),我們就插入 \(q_j\)。
不難證明最后得到的數就是 \(c_1,c_2,\cdots,c_{A-B}\)。因此 \(c_1,c_2,\cdots,c_{A-B}\) 可以作為最終集合存在的充要條件是 \(\forall i\in[1,A-B],c_i\le b_i\)。到這里事情就變得很 trivial 了。\(dp_{i,j}\) 表示考慮了前 \(i\) 個數,\(c_i=j\) 的合法的 \(c\) 的個數,前綴和優化 DP 即可,時間復雜度 \(A^2\)。