zoukankan      html  css  js  c++  java
  • AtCoder Regular Contest 127 题解

    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\)

  • 相关阅读:
    医疗器械那些事
    内审员学习1
    BA
    GMP文件分类与编码管理规程
    杂-电工学
    产品学习1
    模拟电子技术1
    电路组装1
    Unable to simultaneously satisfy constraints.
    xcode
  • 原文地址:https://www.cnblogs.com/ET2006/p/arc127.html
Copyright © 2011-2022 走看看