cfRounddiv3--CDEF题解

C-Assembly via Remainders

思路:因为xi最大只有500,而构造的ai最大可以到1e9,直接从501开始构造即可。

void solve(){           //C  简单构造
    int n; cin>>n;
    vector<int> vct;
    vct.emplace_back(501);
    for(int i=2;i<=n;i++){
        int x; cin>>x;
        vct.emplace_back(vct[i-2]+x);
    }
    for(int i=0;i<n;i++) cout<<vct[i]<<" ";
    cout<<endl;
}

D-Permutation Game

思路:按着顺序枚举即可。枚举每一个位置,可以计算出此后一直拿这个位置可以得到的最终的值。取max即可.计算双方的最大可能得分来回答这个问题。

int p[200005];
int arr[200005];
void solve(){               //D         枚举n,贪心
    int n,k,sa,sb; cin>>n>>k>>sa>>sb;
    for(int i=1;i<=n;i++) cin>>p[i];
    for(int i=1;i<=n;i++) cin>>arr[i];
//    vector<int> pa,pb;
//    pa.emplace_back(sa);
//    pb.emplace_back(sb);
//    for(int i=2;i<=n;i++) pa.emplace_back(p[pa[i-2]]);
//    for(int i=2;i<=n;i++) pb.emplace_back(p[pb[i-2]]);
    int maxa=INT_MIN,suma=0,maxb=INT_MIN,sumb=0;
    for(int i=1;i<=k&&i<=n;i++){
        suma+=arr[sa];
        maxa=max(maxa,suma+(k-i)*arr[sa]);
        sa=p[sa];
        sumb+=arr[sb];
        maxb=max(maxb,sumb+(k-i)*arr[sb]);
        sb=p[sb];
    }
    if(maxa>maxb) cout<<"Bodya"<<endl;
    else if(maxb>maxa) cout<<"Sasha"<<endl;
    else cout<<"Draw"<<endl;
}

E-Cells Arrangement

思路:集合最大的值为(n-1+n-1).选了(1,1)和(1,2)可以贡献0,1;两个值.再选(n,n)可以贡献(n-1+n-1)和(n-1+n-1-1). 再选(n-1,n-1)可以贡献(n-1-1+n-1-1)和(n-1-1+n-1-1-1)都是会贡献两个不重复的,没出现过的值。

举例:n=4; 

选择(1,1)和(1,2)-->S={0,1};

选择(4,4)-->S={0,1,6,5};

再选择(3,3)-->S={0,1,6,5,4,3};  呃呃这个应该是S={0,1,6,5,4,3,2}这样选就产生了所有可能的距离。。 

可是为什么不会存在一个点,选了这个点可以贡献3个没出现过的值的呢?原因如上...呃呃歪打误撞写对了正解。。

主对角线到(1,1)产生偶数距离,到(1,2)产生奇数距离.

void solve(){               //E   巧妙构造题,如果想歪了的话,将会一直卡死  30分钟
    int n; cin>>n;
    cout<<"1 1"<<endl;
    cout<<"1 2"<<endl;
    for(int i=n;i>=3;i--) cout<<i<<" "<<i<<endl;
    cout<<endl;
}

F-Equal XOR Segments

思路:不错的题!

首先区间异或题,99%是需要一个区间前缀和pre

第一步:首先需要 发现 最终区分的区间都可以是两段或三段。

因为大于三段的区间,都可以通过x^x^x=x化简为两段或三段。

发现上面性质之后,就可以进行下一步了。

第二步:思考两段的时候是什么情况,三段的时候又是什么情况。

两段:[l,m] , [m+1,r] --> pre[m]^pre[l-1] == pre[r]^pre[m] --> pre[l-1] == pre[r];  这个o(1)判断即可

三段:[l,a] , [a+1,b] , [b+1,r] --> pre[a]^pre[l-1] == pre[b]^pre[a] == pre[r]^pre[b] --> ①pre[l-1]==p[b] ②pre[r]==pre[a]

   且需要满足条件 l<=a<b<r ; 按定义的话,左边断点a取等就是第一个单独一个区间,右边取等没意义,越界了。第二个断点b取r-1的时候,r就是单独一个区间的含义了。

推出这个之后,就进行可以下一步了,怎么确定有没有合法区间?

第三步:二分。

因为pre[l-1]和pre[r]都是定值,现在需要找到下标a,b满足①pre[l-1]==p[b] ②pre[r]==pre[a].而且b要尽可能小(靠近L),a要尽可能大(靠近R).因为这样才能不错漏答案。这个时候就需要二分来找了,因为下标是有单调性的,可以二分。

第四步:要想到用map<int,vector<int>> mp; 这个容器来存数据。 mp.first是pre[i]的值,mp.second是一个vector<int>,存的是m.first的下标。

////F-Equal XOR Segments-相等的 XOR 段--好题
////手写二分
int n,q;
int pre[200005];            ////经典的异或前缀和
void solve(){               ////F   o(qlogn)  异或+二分(不好想到)
    cin>>n>>q;
    ////关键就在于此。mp是用作二分的容器。 first为前缀异或的值--second为这些相同的前缀异或值出现的下标..(需要对这些下标进行二分)
    map<int,vector<int>> mp;        ////TLE30..cf不要再用unordered_map了。。会专门被卡。。
    for(int i=1;i<=n;i++){
        cin>>pre[i];
        pre[i]^=pre[i-1];
        mp[pre[i]].emplace_back(i);    ////vector中存的是下标
    }
    while(q--){
        int l,r; cin>>l>>r;
        bool ans=false;
        ////先检查能不能分为两段
        if(pre[l-1]==pre[r]) ans=true;
        ////再检查能不能分为三段
        if(!ans){
            int num1=pre[l-1],num2=pre[r];
            int minidxa=INT_MAX,maxidxb=INT_MIN;
//            vector va=mp[num1],vb=mp[num2];       //这个操作是时间复杂度是o(n)的,空间复杂度也是o(n)的
            int ll=0,rr=(int)mp[num2].size()-1;      ////注意这里的ll和rr是 二分的是下标 --- 二分下标找在区间l,r中的下标..
            while(ll<=rr){
                int mid=(ll+rr)>>1;
                if(l<=mp[num2][mid]&&mp[num2][mid]<r){
                    minidxa=mp[num2][mid];
                    rr=mid-1;
                }
                else if(mp[num2][mid]>=r) rr=mid-1;
                else if(mp[num2][mid]<=l) ll=mid+1;
            }
            ll=0,rr=(int)mp[num1].size()-1;      ////超乎想象。。mp.size()不加强制类型转换,在cf上跑会导致RE,找了很久都找不到错。。原来这都会导致RE。
            while(ll<=rr){
                int mid=(ll+rr)>>1;
                if(l<mp[num1][mid]&&mp[num1][mid]<r){
                    ////右边可以不取等号,左边需要取等号。按定义的话,左边取等就是第一个单独一个区间,右边取等没意义,越界了。第二个断点取r-1的时候,r就是单独一个区间的含义了。
                    maxidxb=mp[num1][mid];
                    ll=mid+1;
                }
                else if(mp[num1][mid]>=r) rr=mid-1;
                else if(mp[num1][mid]<=l) ll=mid+1;
            }
            if( l<=minidxa && minidxa<maxidxb && maxidxb<r ) ans=true;
        }
        if(ans) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    cout<<endl;
}
////更短的代码,不用手写二分。
////更多的是注释。解释导致RE的原因
int n,q;
int pre[200005];            ////经典的异或前缀和
void solve(){               ////F   o(qlogn)  异或+二分(不好想到)
	cin>>n>>q;
	map<int,vector<int>> mp;
	////l==1时,pre[l-1]=0这是默认的,但是mp中没有更新mp[0].emplace_back(0),而pre中可能恰好没有为0的值;
	////导致mp[0]是空的,那么mp[0].begin()==mp[0].end();
	////那么pb=*--lower.. 是越界的。
	mp[0].emplace_back(0);				////key!!!!没有这个的话计算pb的时候会导致越界
	for(int i=1;i<=n;i++){
		cin>>pre[i];
		pre[i]^=pre[i-1];
		mp[pre[i]].emplace_back(i);
	}
	while(q--){
		int l,r; cin>>l>>r;
		if(pre[l-1]==pre[r]) cout<<"YES"<<endl;  ////分成两段的条件
		else{  									////如果不能分成两段,检查能不能分成三段.
			////仔细思考想清楚,就明白这两个二分了
			////!!在值为pre[r]的下标中!!,找一个在[l,r]区间找到第一个大于l-1的下标对应的迭代器,*取得该下标.(该点可以和l重合)
			int pa=*lower_bound(mp[pre[r]].begin(),mp[pre[r]].end(),l);   	////这里找的是l
			////!!在值为pre[l-1]的下标中!!,找一个在[l,r]区间找到第一个大于r的下标对应的迭代器,将该迭代器左移一位,*得到一个尽可能接近r,且小于r的下标.
			int pb=*--lower_bound(mp[pre[l-1]].begin(),mp[pre[l-1]].end(),r);  ////这里找的是r
			if( l<=pa && pa<pb && pb<r ) cout<<"YES"<<endl;
			else cout<<"NO"<<endl;
		}
	}
	cout<<endl;
}
////找错...如果是这个道理的话,如果找不到大于r的下标,那么不也会报错吗??。
////回答:不会。二分找不到值的话,会返回mp[pre[x]].end(); .end()是最后一个元素的后一位。 --之后是最后一个元素的迭代器。
////意思是只要mp[pre[x]]不是空的就行。而正常来说是不会空的,只有特殊情况mp[0]是pre[1]-pre[n]没有出现0,但是却会访问pre[0]=0;可能导致mp[0]为空,导致RE
//			auto p1=mp[0].begin();
//			auto p2=mp[0].end();
//			if(p1==p2){			//true
//				cout<<"YESSS";
//				cout<<*p1<<endl;    //RE
//			}

 

posted @ 2024-05-13 00:14  osir  阅读(4)  评论(0编辑  收藏  举报