2021牛客4

题目链接https://ac.nowcoder.com/acm/contest/11255

C - LCS

题目

LCS(s1,s2)表示s1,s2的公共子序列
s1,s2,s3的长度是n
在这里插入图片描述

思路

.
请添加图片描述

代码

#include <bits/stdc++.h>

using namespace std;

int n,a[3];
pair<int,string> p[3];
vector<pair<int,int>> t;
bool cmp(pair<int,int>a,pair<int,int>b)
{
    return a.second<b.second;
}
int main()
{
    cin>>a[0]>>a[1]>>a[2]>>n;
    t.push_back({0,a[0]});
    t.push_back({1,a[1]});
    t.push_back({2,a[2]});
    sort(t.begin(),t.end(),cmp);
    if(t[2].second+t[1].second-n>t[0].second)
    {
        cout<<"NO"<<endl;
        return 0;
    }
    p[0].first=t[0].second;//min
    p[1].first=t[1].second;
    p[2].first=t[2].second;//max
    for(int i=0; i<t[0].second; i++)
    {
        p[0].second+='a';
        p[1].second+='a';
        p[2].second+='a';
    }
    for(int j=0; j<t[2].second-t[0].second; j++)
    {
        p[1].second+='b';
        p[2].second+='b';
    }
    for(int k=0; k<t[1].second-t[0].second; k++)
    {
        p[0].second+='c';
        p[2].second+='c';
    }
    while(p[0].second.size()<n)
    {
        p[0].second+='d';
    }
    while(p[1].second.size()<n)
    {
        p[1].second+='e';
    }
    while(p[2].second.size()<n)
    {
        p[2].second+='f';
    }
    //123
    if(a[0]<a[1] && a[1]<a[2])
    {
        cout<<p[1].second<<endl;
        cout<<p[0].second<<endl;
        cout<<p[2].second<<endl;
        return 0;
    }
    //132
    if(a[0]<a[1] && a[1]>=a[2] && a[0]<a[2])
    {
        cout<<p[0].second<<endl;
        cout<<p[1].second<<endl;
        cout<<p[2].second<<endl;
        return 0;
    }
    //213
    if(a[0]>a[1] && a[1]<a[2] && a[0]<=a[2])
    {
        cout<<p[2].second<<endl;
        cout<<p[0].second<<endl;
        cout<<p[1].second<<endl;
        return 0;
    }
    //231
    if(a[0]<=a[1] && a[1]>a[2] && a[0]>a[2])
    {
        cout<<p[0].second<<endl;
        cout<<p[2].second<<endl;
        cout<<p[1].second<<endl;
        return 0;
    }
    //312
    if(a[0]>a[1] && a[1]<a[2] && a[0]>a[2])
    {
        cout<<p[2].second<<endl;
        cout<<p[1].second<<endl;
        cout<<p[0].second<<endl;
        return 0;
    }
    //321
    if(a[0]>=a[1] && a[1]>=a[2])
    {
        cout<<p[1].second<<endl;
        cout<<p[2].second<<endl;
        cout<<p[0].second<<endl;
        return 0;
    }

    return 0;
}

总结

1.最后比较的时候,分类讨论,注意122和222的情况
2.感觉自己的方法好复杂(⊙﹏⊙)

E - Tree Xor

题目

一棵树,n个结点,每个结点的值为w
已知:w的取值是[l,r],且两个节点u,v的w的异或值
求:w的取值能有多少种可能

思路

  1. 一颗树,如果知道了每两个父子结点之间的关系,那么当根结点确定的时候,整个树上所有的取值就确定了
  2. 初始化:让w[1]=0,求出所有的val[i]
  3. 因为a ^ b = c,a ^ c = b,所以当 w[1]=a 时,w[i] = val[i] ^ a,
    即:
    R[i].l <= val[i]^a <= R[i].r
    即:
    R[i].l^a <= val[i] <= R[i].r^a
  4. 找出每一个区间之后,求交集,交集的个数==n的时候,满足

关键点是如何构造一段区间,使这段区间^a之后,仍然是连续的

根据题目的范围,在[0,230-1]的范围上,建线段树,其中,每段的范围都是[…000,…111]的形式(…表示前面都是相同的01序列),这样,当这个区间与a异或的时候,以为前几位一直都是相同的,所以得到的还是一个连续的区间
在这里插入图片描述
注意左右端点的找法:

     //pos是从后往前,不同的位数
     int LL=( val^l )&( ((1<<30)-1)^((1<<pos)-1) );
     int RR=LL+((1<<pos)-1); 

LL分成两部分:…000,假设…长度为4,…是这段区间异或a,然后把后面pos=3位全部置0
RR是右端点,直接…000(LL)+…111=…111

样例

4
0 7
1 6
2 5
3 4
1 2 0
1 3 7
2 4 6

请添加图片描述

代码

#include <bits/stdc++.h>
#define maxx 1000050
using namespace std;
typedef long long ll;
int n,cnt=0,head[maxx],val[maxx];

vector<pair<int,int>> vec;
struct range
{
    ll l,r;
}R[maxx];

struct node
{
    int u,v,next;
    ll w;
}e[maxx];

void add(int u,int v,int w)
{
    e[cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
    cnt++;
}

void init(int u,int pre,int w)
{
    val[u]=val[pre]^w;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int vv=e[i].v;
        int ww=e[i].w;
        if(pre==vv)
            continue;
       init(vv,u,ww);
    }
}

void query(int L,int R,int val,int l,int r,int pos)
{
    if(L<=l && R>=r)
    {
        int LL=( val^l )&( ((1<<30)-1)^((1<<pos)-1) );
        int RR=LL+((1<<pos)-1);
        vec.push_back({LL,RR});
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)
        query(L,R,val,l,mid,pos-1);
    if(R>mid)
        query(L,R,val,mid+1,r,pos-1);

}

int solve()
{
    vector<pair<int,int>> ans;
    for(int i=0;i<vec.size();i++)
    {
        ans.push_back({vec[i].first,1});
        ans.push_back({vec[i].second+1,-1});
    }
    int num=0;
    int nn=0;
    sort(ans.begin(),ans.end());
    for(int j=0;j<ans.size();j++)
    {
        nn+=ans[j].second;
        if(nn==n)
            num+=ans[j+1].first-ans[j].first;
    }
    return num;
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
       cin>>R[i].l>>R[i].r;
    }
    memset(head,-1,sizeof head);
    for(int i=0;i<n-1;i++)
    {
       int u,v,w;
       cin>>u>>v>>w;
       add(u,v,w);
       add(v,u,w);
    }
    init(1,0,0);//w[1]=0时的取值
    vec.push_back({R[1].l,R[1].r});
    for(int i=2;i<=n;i++)
    {
       query(R[i].l,R[i].r,val[i],0,(1<<30)-1,30);
    }
    cout<<solve()<<endl;
    return 0;
}

总结

1.区间的划分
2.异或
3.区间异或时,左右点的找法

F - Just a joke

题目

Alice and Bob又开始玩游戏了,在一个有n个点,m条边的无向图中,可以进行两种操作:

  1. Select an edge of G and delete it from G.

  2. Select a connected component of G which doesn’t have any loop, then delete it from G.

问最后谁赢

思路

题如其名,根据样例可以推断粗,点也是可以删除的。。(・∀・(・∀・(・∀・*)
对于1,删除1或3(一个点或者一条边)
对于2,删除2k-1
都是奇数
所以只跟n+m的奇偶有关

I - Inverse Pair

题目

数组a是1~n之间的数,只能对每个数进行+1或+0操作,求最小的逆序对个数

思路

没有重复的数,而且是1-n之间所有的数,对于xi和xi+1,如果前者大,给后者+1即可,如果有一串的话,如5,4,3最后也是可以抵消的,然后用树状数组

代码

#include<bits/stdc++.h>
#define maxx 200010
typedef long long ll;
using namespace std;

int n;
ll a[maxx],c[maxx],pos[maxx];

int lowbit(int x)
{
    return x&-x;
}

ll add(ll x)
{
    ll ans=0;
    for(;x;x-=lowbit(x))
    {
        ans+=c[x];
    }
    return ans;
}

void add1(ll x)
{
    for(;x<=n;x+=lowbit(x))
    {
        c[x]++;
    }
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        pos[a[i]]=i;
    }
    int f=0;
    for(int i=1;i<n;i++)
    {
        if(pos[i+1]<pos[i] && (f==0))
        {
            a[pos[i]]++;
            f=1;
        }
        else
            f=0;
    }
  // for(int i=1;i<=n;i++)
   //     cout<<a[i]<<' ';
    ll sum=0;
    for(int i=1;i<=n;i++)
    {
        sum+=add(n)-add(a[i]);
       // cout<<add(a[i])<<endl;
        add1(a[i]);
    }
    cout<<sum<<endl;
    return 0;
}

J - Average

题目

就是求数组a在区间≥x时的最大平均值,和,数组b在区间≥y时的最大平均值

思路

前缀和+二分

  1. 假设平均值为T,要找到是否有 a i + . . . + a j L > = T \frac{a_{i}+...+a_{j}}{L}>=T Lai+...+aj>=T
  2. 即 : ( a i − T ) + . . . + ( a j − T ) > = 0 (a_{i}-T)+...+(a_{j}-T)>=0 (aiT)+...+(ajT)>=0
  3. c i = a i − T c_{i}=a_{i}-T ci=aiT,就是求 c i + . . . + c j > = 0 c_{i}+...+c_{j}>=0 ci+...+cj>=0
  4. 通过前缀和可以转换为: s u m [ j ] − s u m [ i − 1 ] > = 0 sum[j]-sum[i-1]>=0 sum[j]sum[i1]>=0

代码

#include <iostream>
#define maxx 100050
using namespace std;

double a[maxx],b[maxx],c1[maxx],c2[maxx],sum1[maxx],sum2[maxx];

int main()
{
    int n,m,x,y;
    cin>>n>>m>>x>>y;

    for(int i=1; i<=n; i++)
    {
        cin>>a[i];
    }

   for(int i=1; i<=m; i++)
   {
       cin>>b[i];
  }

    double l=-1e10, r=1e10;
    while(r-l>1e-7)
    {
        double mid=(l+r)/2;
        for(int i=1;i<=n;i++)
        {
            c1[i]=a[i]-mid;
            sum1[i]=sum1[i-1]+c1[i];
        }
    
        double MIN=(1<<31)-1, MAX=-1e10;
        for(int i=x;i<=n;i++)
        {
            MIN=min(MIN, sum1[i-x]);
            MAX=max(MAX, sum1[i]-MIN);
        }
        if(MAX>=0)
        {
            l=mid;
        }
        else
        {
            r=mid;
        }
    }
    //printf("%.10f\n",r);
    double l2=-1e10, r2=1e10;
    while(r2-l2>1e-7)
    {
        double mid=(l2+r2)/2;
        for(int i=1;i<=m;i++)
        {
            c2[i]=b[i]-mid;
             sum2[i]=sum2[i-1]+c2[i];
        }
       
        double MIN=(1<<31)-1, MAX=-1e10;
        for(int i=y;i<=m;i++)
        {
            MIN=min(MIN, sum2[i-y]); //复制粘贴的时候,没把y改过来。。
            MAX=max(MAX, sum2[i]-MIN);
        }
        if(MAX>=0)
        {
            l2=mid;
        }
        else
        {
            r2=mid;
        }
    }
   // printf("%.10f\n",r2);
    printf("%.8f\n",r2+r);
    return 0;
}

总结

以后要是同一个函数使用两次或多次的时候,不要复制粘贴,写个函数吧(T_T)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值