Codeforces Round #613 (Div. 2)

A

Mezo Playing Zoma

A:LR中选择任意步,求最后到达的点的数量

题解:简单题,统计LR数量加上原点即可

B

Just Eat It!

B:简单题,问是否有连续子段比全部元素之和大、

题解:求最长连续子段[l+1,r]与[l,r-1]两次即可

C

Fadi and LCM

C:给定n=lcm(a,b),要使max(a,b)尽 可 能 小

题解:注意到最大的最小,也就是两个数是n的因子,那么直接枚举sqrt(n)个较小的因子,判断是否lcm是n,然后取最小即可

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef pair<ll,int> PII;
ll n,m,k,t;

int main(void){
    cin>>n;
    ll maxx = n;
    if(n == 1){
        cout<<"1 1\n";
        return 0;
    }
    ll x,y;
    for(ll i = 1;i <= n/i;i++){
        if(n%i == 0){
           y = n/i;
           x = i;
           if(x*y == n*__gcd(x,y)){
                maxx = min(max(y,i),maxx);
           }
        }
    }
    cout<<n/maxx<<' '<<maxx<<endl;
    return 0;
}
D

Dr. Evil Underscores

D:有一个长度为 n 的整数序列,要找到一个非负整数 X 使得 max(ai​⊕X) 最小,输出最小值

题解:输入的数据有1e5,每个元素又有ai <= 2^30-1,对每一个数进行亦或运算,考虑位运算,从最高位开始考虑,一开始考虑直接最高位,但是要是有111,110,011,010这类,明显是要大于100的,所以考虑建树,在Trie字典树上DP,每一位要是只有1或者0就是0,要是有1或者0就DP

这里介绍Trie树:本质上是一棵多叉树,例如对于英文,son[N][26]是节点,有26个分支,前面是idx编号,比如son[2][26] = 10表示2的字母z的子节点是10,只要节点是0表示结束

这里建子节点是01的类似二叉树的字典树:注意到位数总共30,当前二进制位置都是1的话,就选择1,都是0就选择0,两种都有就取1<<k+最小值

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <map>
using namespace std;
typedef long long ll;
const int maxn = 100100;

int n,m,k,t;

ll a[maxn],tr[30*maxn][3],idx;

void insert_trie(ll x){
    int p = 0;
    for(int i = 30;i >= 0;i--){
        int tmp = (x>>i)&1;
        if(!tr[p][tmp])tr[p][tmp] = ++idx;
        p = tr[p][tmp];
    }
}


ll solve(int p,int k){
	if(k < 0) return 0;
	if(!tr[p][1]) return solve(tr[p][0],k-1);///如果只有1就选择0,下一位
	if(!tr[p][0]) return solve(tr[p][1],k-1);///如果只有0就选择1,下一位
	return (1<<k)+min(solve(tr[p][0],k-1),solve(tr[p][1],k-1));///如果0与1都有,就取1<<k+最小值
}

int main(void){
	cin>>n;
	for(int i = 1;i <= n;++i){
		scanf("%lld",&a[i]);
		insert_trie(a[i]);
	}
	cout<<solve(0,30)<<endl;
}

当然,也可以在跑的过程中建字典树,但是不如预先处理树之后快

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <map>
using namespace std;
typedef long long ll;
const int maxn = 100100;

ll n,m,k,t;


ll solve(vector<ll> tmp,int k){
	if(k < 0) return 0;
	vector<ll> p1,p0;
	for(int i = 0;i < tmp.size();i++){
        if((tmp[i]>>k)&1)p1.push_back(tmp[i]);
        else p0.push_back(tmp[i]);
	}
	if(!p1.size()) return solve(p0,k-1);///如果只有1就选择0,下一位
	if(!p0.size()) return solve(p1,k-1);///如果只有0就选择1,下一位
	return (1<<k)+min(solve(p1,k-1),solve(p0,k-1));///如果0与1都有,就取1<<k+最小值
}

vector<ll> a;

int main(void){
	cin>>n;
	for(int i = 1;i <= n;++i){
		scanf("%lld",&t);
		a.push_back(t);
	}
	cout<<solve(a,30)<<endl;
}
E

Delete a Segment

E:给定n个线段覆盖在数轴上,问随便删除一个,剩下的不连续段数最多是多少

题解:先考虑区间修改,然后每次在n个区间中选择一个,该线段在数轴上删除,统计剩余的覆盖片段数量,时间复杂度是o(n^2*数据结构时间)这里的数据结构可以是并查集,也可以是线段树,明显超时,考虑其他方法,这里考虑类似贪心的做法,排序之后从左往右,ans记录总共线段数量,add_union记录删除时候会增加或者减少的区间数量,取最大值再加上原来ans得到答案

ans的做法是遍历序列,只要没有括号嵌套,说明是一段结束了,ans++

add_union考虑三种情况

情况1:

(1)-1   -2  2   -3  这样嵌套,那么id2的那一段在id1段中,而且id2与id3的那一段分离,所以删除1在这里可以+1区间数

(2)-1   -2   1  -3  这样嵌套,那么id1段与id3段分离,删除id2段可以增加1

注意到括号匹配之后剩下的首元素恰好是最开始入栈的元素,这里用multiset模拟可以查看首元素,修改里面的元素的栈,首元素与后面的比较判断情况1

情况2:

-1    1 这样id1的一段删除之后贡献是-1,直接记录

情况3:

-1   -2   1    2,这里2或者1对总线段数的贡献都是0,没有必要记录,删除id2段剩下id1段,反之亦然

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <set>
using namespace std;
typedef long long ll;
const int N = 400100;

pair<int,int> a[N];
int add_union[N];

int n,m,k,t,l,r,ans1,ans2;

int main(void){
    cin>>t;
    while(t--){
        int ans = 0;
        cin>>n;
        for(int i = 1;i <= n;i++){
            cin>>l>>r;
            a[i*2-1] = {l,-i};///将左端点排序
            a[i*2] = {r,i};///i是用来匹配
            add_union[i] = 0;
        }
        sort(a+1,a+1+2*n);///端点从大到小,左端点优先
        multiset<int>s;///最左侧未匹配左端点所在区间
        for(int i = 1;i <= n*2;i++){///左右端点共2n个点
            if(a[i].second < 0)s.insert(-a[i].second);///左端点存储
            else s.erase(s.find(a[i].second));

            if(!s.size())ans++;///单个区间
            if(s.size()==1 && a[i].second>0 && a[i+1].second < 0 && a[i+1].first  > a[i].first)//[ [ ]  也就是可以多一队,下一个端点为[
                add_union[*s.begin()]++;
            if(s.size()==1 && a[i].second<0 && a[i+1].second>0)//当前[ 下一个端点],删了就少一队
                add_union[*s.begin()]--;
        }
        int num = -0x3f3f3f3f;
        for(int i = 1;i <= n;i++){
            num = max(num,add_union[i]);
        }
        cout<<num+ans<<"\n";
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值