7.14二分,LCA,差分,思维构造

把线段树和LCA复习了一下

D. Fixed Point Guessing
二分+交互题,算是一个简单点的题目

#include<iostream>
using namespace std;
bool check(int l,int mid)
{
    int ans=0;
    cout<<"? "<<l<<' '<<mid<<endl;
    for(int i=l;i<=mid;i++)
    {
        int x;
        cin>>x;
        if(x<l||x>mid)
        {
            ans++;
        }
    }
    return ((mid-l+1-ans)&1);
}
int main(){
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        int l=1,r=n;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(check(l,mid))
            {
                r=mid;
            }
            else
            {
                l=mid+1;
            }
        }
        cout<<"! "<<l<<endl;
    }
}

C. The Third Problem

题意:给定一个长度为N的排列,问你这个排列可以有几种排列方式,要求所有的方式下,各个区间的MEX均相同

思路:首先观察这个MEX,各个区间的MEX不变,这是问题的特殊要求,也是问题的切入点,
根据样例观察分析,比如0 1 4 3 2 就有 0 1 3 4 2等选择,为什么中间的数可以交换呢?因为中间的数交换后反正左右两边都有比这俩小的,根本就不影响MEX。
我们需要的就是确定,如果对于一个数,左右两边都有比这个数小的数,那么这个数就可以在最左和最右比他小的数的区间之内进行和其他数的交换,以此来增加方案数。

增加的方案数是多少呢,我们先确定区间的长度是len=r-l+1。
对于这个len,我们可以保证的是,如果遍历到的数在这这内,那么这个数一定小于len,用len-x就得到了这个区间内,还有多少可以和len交换的数。

#include <iostream>

#define int long long

using namespace std;
const int N = 1e5+100;
const int mod = 1e9+7;
int a[N];
int pos[N];
signed main()
{
    int t;
    for(cin>>t;t;t--)
    {
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
            pos[a[i]]=i;
        }
        int l,r,ans=1;
        l=r=pos[0];
        for(int i=1;i<n;i++)
        {
            if(l>pos[i])
                l=pos[i];
            else if(r<pos[i])
                r=pos[i];
            else
                ans=ans*(r-l+1-i)%mod;
        }
        cout<<ans<<endl;
    }
    return 0;
}

C. Helping the Nature

题意:给定一个数组,我们每次选一个点,下标为i,那么我们可以选择三个操作
1.从i到n全体建议
2.从1到i全体减一
3.全数组加一

要求全数组变成0,最少需要多少次操作

这里有个小结论吧,最少操作次数就是先变成一个统一的值然后再变成0,问题就变成了如何变成统一值操作次数最少,这个显然就是两两之间的差距值了。

做差分数组d。
我们假设是从后往前把数组变成统一值,以倒数一二两项为例,如果D[n]>0,我们可以将后面的数缩小,也就是把从n-1到第n项减小,减D[n]的值就好
如果D[n]<0,那就只好把前面缩小了喽,不过注意,我们不能连带第一项缩小,并且要把这个变化在第一项记录好,这个记录实际上就是差分数组对于2到n-1区间的减小过程。

逐步加和即可。

#include <iostream>
#define int long long
using namespace std;
const int N=2e5+100;
int a[N];
int b[N];
signed main()
{
    int t;
    for(cin>>t;t;t--)
    {
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            b[i]=a[i]-a[i-1];
        }
        int res=0;
        for(int i=n;i>1;i--)
        {
            if(b[i]>=0)
                res+=b[i];
            else
            {
                res-=b[i];
                b[1]+=b[i];
            }
        }
        cout<<res+abs(b[1])<<endl;
    }
    return 0;
}

F. Equate Multisets
说是a和b两个数组,我们可以对b数组中的每个元素无限次进行向下取整除二,和乘而两种操作。
问能否把b变成a。

把a中的元素在除二没有余数的情况下尽可能的除二,就能得到a的“基值"
对于这样,我们只要能在b中获取这样的基值就一定可以获得原本a中的数值,也就省去了乘2的操作。
这个题的关键就是看头乘二操作无用。

当b获得一个a的基值,自动删去一个,如果b搞了一个a没有的数值并且这个数是1很明显就是无解了。
否则把这个数值删掉,并且把这个数值的除二结果压进去,记住每次都要选取b中最大的那个数来做操作。

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;

int n;
map<int,int> a;
priority_queue<int> b;
int main()
{
    int t;
    for(cin>>t;t;t--)
    {
        a.clear();
        while(!b.empty())b.pop();
        scanf("%d",&n);
        for(int i=1,temp;i<=n;i++)
        {
            scanf("%d",&temp);
            while(temp%2==0)
            {
                temp>>=1;
            }
            a[temp]++;
        }
        for(int i=1,temp;i<=n;i++)
        {
            scanf("%d",&temp);
            b.push(temp);
        }
        while(!b.empty())
        {
            int x=b.top();
            if(a[x]==0)
            {
                if(x==1)
                    break;
                b.pop();
                b.push(x>>=1);
            }
            else
            {
                a[x]--;
                b.pop();
            }
        }
        if(b.empty())
        {
            cout<<"YES"<<endl;
        }
        else
        {
            cout<<"NO"<<endl;
        }
    }
    return 0;
}

G2. Passable Paths (hard version)
题意:给定一个有根数,根为1,问k次,每次询问一堆点是否在一条简单路径上,在输出YES,不在输出NO

思路:初步想用LCA的方法做,对于询问的各个点应该根据深度进行排序,具体还没想好,做了一个小时WA掉了,明天补上。

没调通的废物代码。

#include <iostream>

using namespace std;
struct node
{
    int nex,to;
};
const int N = 5e5+100;

node edge[N<<1];
int p[N];
int dep[N],head[N],tot;
int siz[N],son[N],top[N],fa[N];
void add(int from,int to)
{
    edge[++tot].nex=head[from];
    edge[tot].to=to;
    head[from]=tot;
}

void DFS1(int now,int fath)
{
    fa[now]=fath;
    siz[now]=1;
    son[now]=0;
    dep[now]=dep[fath]+1;
    for(int i=head[now];i;i=edge[i].nex)
    {
        if(edge[i].to==fath)
        {
            continue;
        }
        DFS1(edge[i].to,now);
        siz[now]+=siz[edge[i].to];
        if(siz[son[now]]<siz[edge[i].to])
        {
            son[now]=edge[i].to;
        }
    }
}
void DFS2(int now,int topx)//topx,先重儿子再轻儿子
{
    top[now]=topx;
    if(son[now])
    {
        DFS2(son[now],topx);
    }
    else
        return ;
    for(int i=head[now];i;i=edge[i].nex)
    {
        if(edge[i].to!=fa[now]&&edge[i].to!=son[now])
        {
            DFS2(edge[i].to,edge[i].to);
        }
    }
}
int LCA(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])
        {
            swap(x,y);
        }
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}
int main()
{
    int n;
    cin>>n;
    for(int i=1,a,b;i<=n-1;i++)
    {
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    DFS1(1,0);
    DFS2(1,1);
    int m;
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        int x,mindep=100000000;
        cin>>x;
        for(int j=1;j<=x;j++)
        {
            cin>>p[j];
            if(mindep>dep[p[j]])
                p[0]=p[j];
        }
        bool falg=true;
        for(int k=1;k<=x;k++)
        {
            if(LCA(p[0],p[k])!=p[0])
            {
                cout<<"NO"<<endl;
                falg=false;
                break;
            }
        }
        if(falg)
        cout<<"YES"<<endl;
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值