Codeforces Round #765 (Div. 2)

Codeforces Round #765 (Div. 2)

写完ABC,又罚座了....

D. Binary Spiders

先看这个题,题意很简单,给你一堆数,让你选出一个集合,使得这个集合内任意两个数异或的值都大于等于k,最后问这个集合的最大数量以及一个构造方案。
我们考虑大于k有哪些情况,由于和位运算有关系,我们肯定要从二进制的角度去解析。我们先把k换成二进制位,我们找到它的1的最高位,那么如果一个数x要大于k的话,可以分为以下两种简单情况
1.x的1的最高位大于k的1的最高位。
2.x的1的最高位等于k的1的最高位。之后去掉最高位,再做比较。
我们先考虑第一种情况,要求这个集合内,任意两个数异或起来都是大于等于k的。第一种情况也可以换一种情况表述:将x向右移m位,m为k的1的最高位,x仍>0.也就是说,任意两个数向右移m位后,异或起来仍大于0,考虑什么情况下,两个数异或起来等于0,也就是相等的情况。那么思路就有了,我们可以把所有的数按照向右移m位后的值进行分类,不同类之间,异或起来一定大于0,则意味着情况1成立,他们互不干扰。那么同一类里面,最多能选多少个呢?考虑三个数,他们是同一类的,a,b,c,假如他们都可以被选中的话,那么在m位上他们必定ab=1,ac=1,b^c=1.那么他们中一定有两个数在m位上异或起来等于0,小于k,这说明同一类中最多选2个。至于能不能选,这就要看这一类中最大的异或值了。这是个经典问题,建个0/1 tire树即可。
总的来说这个题真挺好的。

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int n,k,tot,num,m,t[N*32][3];
pair<int,int>ed[N*32]; 
map<int,int>mp;
vector<pair<int,int>>v[N];
vector<int>ans;
inline pair<int,int> ask(pair<int,int> x)
{
    int p=1;
    for(int i=30;i>=0;--i)
    {
        int ch=((1<<i)&x.first)?1:0;
        if(t[p][ch^1]) p=t[p][ch^1];
        else p=t[p][ch];
    }
    return ed[p];
}
inline void insert(pair<int,int> x)
{
    int p=1;
    for(int i=30;i>=0;--i)
    {
        int ch=((1<<i)&x.first)?1:0;
        if(!t[p][ch]) t[p][ch]=++tot;
        p=t[p][ch];
    }
    ed[p]=x;
}
inline void work(int id)
{
    if(v[id].size()==1) 
    {
        ans.push_back(v[id][0].second);
        return;   
    }
    tot=1;
    for(auto x:v[id]) insert(x);
    int an=-1,p1,p2;
    for(auto x:v[id]) 
    {
        pair<int,int> p=ask(x);
        if((x.first^p.first)>an) 
        {
            an=x.first^p.first;
            p1=x.second;p2=p.second;
        } 
    }
    if(an>=k) ans.push_back(p1),ans.push_back(p2);
    else ans.push_back(v[id][0].second);
    for(int i=0;i<=tot;++i) t[i][0]=0,t[i][1]=0,ed[i]={0,0}; 
    return;
}
int main()
{
    scanf("%d%d",&n,&k);
    if(k==0) 
    {
        printf("%d\n",n);
        for(int i=1;i<=n;++i) printf("%d ",i);
        return 0;
    }
    for(int i=30;i>=0;--i) if((1<<i)&k) {m=i;break;}
    for(int i=1;i<=n;++i)
    {
        int x;scanf("%d",&x);
        int sx=x>>(m+1);
        if(mp.find(sx)==mp.end()) mp[sx]=++num;
        v[mp[sx]].push_back({x,i});
    }
    for(int i=1;i<=num;++i) work(i);
    if(ans.size()<=1) {puts("-1");return 0;}
    printf("%d\n",ans.size());
    for(auto x:ans) printf("%d ",x);
    return 0;
} 
 

E1. Cats on the Upgrade (easy version)

和其他的一些求方案数的题一样,不要怕,总之就是可以先把大致的东西设出来,再细致的考虑到底那些东西重复了,需要减去即可。
同样的,这个题可以发现是一个括号匹配的问题,一个很重要的思想就是将括号匹配的问题看成进栈出栈,然后转化成树上问题,这样就好处理多了。
不过这个题,我对于树上的比较迷,就还是用方案数类的计数的方法做了。
首先题目询问的是以l-r内开头,以l-r内结尾的合法的括号数。那么我们可以首先搞出p[i]为以i结尾的合法的括号数。这个其实也好搞,如果当前是右括号的话,我们找到它对应的左括号的左边的括号,则当前有括号的p为前面的方案数加1.用栈就能很好的完成。之后可以搞出p数组的前缀和sum,表示以前i结尾的合法的括号数。那么对于一个询问l,r,我们可以用sum[r]-sum[l-1]可以得出以[l,r]为结尾的合法的方案数。但这个显然不是答案,因为这些方案数中某些方案可能不是以[l,r]为开头的。
那么我们可以观察一下这些不符合条件的方案有哪些特殊的性质即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e5+10;
int n,q,p[N],st[N],top,a[N];
ll sum[N];
char c[N];
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d%d%s",&n,&q,c+1);
    for(int i=1;i<=n;++i)
    {
        if(c[i]=='(') st[++top]=i;
        else
        {
            if(!top) st[++top]=i;
            else
            {
                if(c[st[top]]=='(')
                {
                    top--;
                    p[i]=a[st[top]]+1;
                    a[st[top]]++; 
                }
                else st[++top]=i;
            }
        }
    }
    for(int i=1;i<=n;++i) sum[i]=sum[i-1]+p[i];
    for(int i=1;i<=q;++i)
    {
        int t,l,r;scanf("%d%d%d",&t,&l,&r);
        printf("%lld\n",sum[r]-sum[l-1]-p[l-1]*(p[r]-p[l-1]));
    }
}
posted @ 2022-01-15 11:21  逆天峰  阅读(98)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//