牛客题单——前缀、差分、二分、排序、离散化

题单链接

七夕祭

这道题的一个前置题目叫均分纸牌,是一道经典的贪心题,没有做过的建议先把那道题搞懂再看此题嗷!

点这里就可以看我写过的均分纸牌,不喜勿喷

在这到题目中,每次操作只能对行或者对列进行操作,两者相互不影响,因此我们可以将题目分成两部分,在满足条件的情况下依次处理行和列即可
对于判断能否成立还是比较容易的,只需要判断在同一行的点能否均分均分即可,在列中也是一样,关键是怎么求得移动所需的步骤呢?

这里我们以行为例进行分析,这个题与均分纸牌那道题唯一的区别就是成环了!非常容易就可以想到一个非常暴力的想法,我们从对于每个点都求得在这个点处断开所需要的移动次数,然后在所有的移动次数中求取最小值即可
那我们来想怎么去优化这个过程呢?
非常容易证明啊,我们只需要在中位数的地方断开即可得到最小值,剩下的和均分纸牌那道题就是一摸一样的了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, m, t;
int h[N], z[N];
int f[N];
int sh, sz;
ll op(int a[], int n, int sum)
{
    
    
    ll res = 0;
    memset(f, 0, sizeof(f));
    for (int i = 1; i <= n; i++)
    {
    
    
        a[i] -= sum / n;
        f[i] = f[i - 1] + a[i];
    }
    sort(f + 1, f + 1 + n);
    for (int i = 1; i <= n; i++)
        res += abs(f[i] - f[(n + 1) >> 1]);
    return res;
}
int main()
{
    
    
    cin >> n >> m >> t;
    for (int i = 0; i < t; i++)
    {
    
    
        int x, y;
        cin >> x >> y;
        h[x]++, z[y]++;
    }
    for (int i = 1; i <= n; i++)
        sh += h[i];
    for (int i = 1; i <= m; i++)
        sz += z[i];
    if (sh % n == 0 && sz % m == 0)
        cout << "both" << " " << op(h, n, sh) + op(z, m, sz) << endl;
    else if (sh % n == 0)
        cout << "row" << " " << op(h, n, sh) << endl;
    else if (sz % m == 0)
        cout << "column" << " " << op(z, m, sz) << endl;
    else
        cout << "impossible" << endl;
    return 0;
}

奇数码问题

首先将二维的矩阵转换成一维的数组
首先如果0在行上面移动,很容易证明这样不会改变除0之外序列的逆序对
往下如果0在列上面移动,这样也是不改变除0之外序列的逆序对的,下面给出证明:

1、由于给出方阵的行列都是奇数,那么同一列上面的两个数转化成一维后它们两个之间必然有偶数个数
2、假设要操作的同一列上的两个数是a和b,在[a,b]这个区间内有x个比b大的数,y个比b小的数
3、如果b到a的位置,那么对于b会减少y个逆序,增加x个逆序,这样会导致逆序对的变化是x-y
4、由于题中说明了整个方阵中没有重复的元素,因此区间[a,b]中的数要么比y大要么比y小,因此x+y是一个偶数,因此x和y要么同为奇数要么同为偶数,因此x-y一定是一个偶数
5、题中所给操作必须是对0进行操作,因此区间[a,b]中a=0,区间中所有的数全部都大于0,因此对0操作对逆序的改变必然是区间长度,区间长度必为偶数
6、对于0的操作必然是可逆的,因此[0,b]和[b,0]对逆序的改变互为相反数,因此奇偶性不变
7、综上所述,0在列上移动不改变逆序数

因此可以用求逆序的方式得到答案,逆序奇偶性相同,则可达,反之则不可达

我看到的博客最终都是证明到这一步,但是我认为这样的证明过程有些自欺欺人,因为这里并没有论证清楚为什么奇偶性相同的方阵之间是相互可达的

这里我尝试给出这个证明:
先写出结论吧:奇偶性相同的方阵之间是相互可达的,奇偶性不同的方阵之间是相互不可达的!
这里我先是写了一个暴搜程序,跑结果巨慢巨慢!而且数据量不能太大!最终的结果确实是上述结论,有兴趣的话可以自己写一个试试看
回归正题!证明!不喜勿喷!

1、首先将二维矩阵转化成一维数组后,可以通过题目中给的两种操作将任意一个数移动到一个最后一个位置
2、然后我们将最大的一个数移动到最后一个位置,第二大的数移动到倒数第二个位置,以此类推
3、对于任意的一个序列我们按照这样的方式一直递归下去,我们就必然会得到两种结果之一。第一种:0 1 2 …,第一种0 2
1…,分别对应两种奇偶性不同的方阵,而这种最终形态无法完成相互转化 4、因此奇偶性相同的方阵之间是相互可达的,奇偶性不同的方阵之间是相互不可达的

扫描二维码关注公众号,回复: 12506643 查看本文章
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 500*500+10;
int n;
int tr[N];
int lowbit(int x)
{
    
    
    return x & -x;
}
void add(int x, int c)
{
    
    
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}
int sum(int x)
{
    
    
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
        res += tr[i];
    return res;
}
int main()
{
    
    
    while(cin>>n)
    {
    
    
        n=n*n;
        memset(tr,0,sizeof(tr));
        ll res1=0;
        for(int i=1;i<=n;i++)
        {
    
    
            int x;
            scanf("%d",&x);
            if(x)
            {
    
    
                res1+=sum(n-1)-sum(x);
                add(x,1);
            }
        }
        memset(tr,0,sizeof(tr));
        ll res2=0;
        for(int i=1;i<=n;i++)
        {
    
    
            int x;
            scanf("%d",&x);
            if(x)
            {
    
    
                res2+=sum(n-1)-sum(x);
                add(x,1);
            }
        }
        if((res1%2)==(res2%2)) cout<<"TAK"<<endl;
        else cout<<"NIE"<<endl;
    }
    return 0;
}

Cinema

这道题是一道典型的考察离散化的题

首先看到这道题第一想法就是桶排序+结构体排序
但是一看数据范围,我们在进行桶排序的过程中无法开到一个这么大的数组
因此这里进行离散化,我直接用的stl中的哈希表写的
下面就是代码了

#include <bits/stdc++.h>
using namespace std;
const int N=2*1e5+10;
int n,m;
unordered_map<int,int>vis;
struct sa
{
    
    
    int vo;
    int wi;
    int pos;
}bc[N];
int cmp(const sa &a,const sa &b)
{
    
    
    if(a.vo!=b.vo) return a.vo>b.vo;
    else return a.wi>b.wi;
}
int main()
{
    
    
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
    
    
        int x;
        scanf("%d",&x);
        vis[x]++;
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
    
    
        int x;
        scanf("%d",&x);
        bc[i].pos=i;
        bc[i].vo=vis[x];
    }
    for(int i=1;i<=m;i++)
    {
    
    
        int x;
        scanf("%d",&x);
        bc[i].wi=vis[x];
    }
    sort(bc+1,bc+1+m,cmp);
    cout<<bc[1].pos<<endl;
    return 0;
}

Best Cow Fences

最佳牛围栏
首先程序的主体是一个二分,通过二分得到最终结果

这道题比较麻烦的地方就是在chack函数上,如何判断一个解是否能成立呢?
首先在这里要求区间的和就不免会想到数组的前缀和
在这里我们可以将数组中每一个数都减去均值后构建前缀和数组
这样sum[j]-sum[i]>=0就表示区间[i,j]的区间和是大于均值的

具体的判断过程用双指针算法解决
指针i记录i之前的前缀和最小值,当sum[j]-minx>0表示当前的这个均值是能成立的

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const double eps=1e-5;
int n,f;
int x[N];
double sum[N];
bool check(double res)
{
    
    
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+x[i]-res;
    double minx=0;
    for(int i=0,j=f;j<=n;i++,j++)
    {
    
    
        minx=min(minx,sum[i]);
        if(sum[j]>minx) return 1;
    }
    return 0;
}
int main()
{
    
    
    cin>>n>>f;
    double l=0,r=2000;
    for(int i=1;i<=n;i++) cin>>x[i];
    while(r-l>eps)
    {
    
    
        double mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    cout<<(int)(r*1000)<<endl;
    return 0;
}

IncDec Sequence

这道题是差分的典型应用
首先对原数组构造差分数组,要想经过变换后每一项都相等,就需要差分数组的2~n项都是0
因此对于这道题就是求怎么让差分数组的2~n项都是0都变成0

首先最优的情况就是一正一负的去找
上一种做法做到最后不一定2~n中的每一项都会变成0,这样我们就需要这些项和第一项或者第n+1项进行操作,这里就同时有两种选择,这样的两种不同选择就会导致最后不同的结果

以下是代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N];
int n;
int main()
{
    
    
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=n;i>1;i--) a[i]=a[i]-a[i-1];
    ll zh=0,fu=0;
    for(int i=2;i<=n;i++)
    {
    
    
        if(a[i]>0) zh+=a[i];
        else fu-=a[i];
    }
    cout<<abs(zh-fu)+min(zh,fu)<<endl;
    cout<<abs(zh-fu)+1<<endl;
    return 0;
}

Running Median

这道题使用的是优先队列
建立一个升序优先队列(小根堆) 一个降序优先队列(大根堆)

同时保证小根堆中的元素大于大根堆中的元素
并且小根堆中的元素个数永远比大根堆中元素的个数多一个

由此每次只需要输出小根堆的堆顶即可得到当前的中位数

#include <bits/stdc++.h>
using namespace std;
int p;
int t, m;
int main()
{
    
    
    scanf("%d", &p);
    while (p--)
    {
    
    
        priority_queue<int> visbig;
        priority_queue<int, vector<int>, greater<int>> vissmall;
        scanf("%d%d", &t, &m);
        printf("%d %d\n",t,(m+1)/2);
        int num=0;
        for(int i=1;i<=m;i++)
        {
    
    
            int x;
            scanf("%d",&x);
            //cout<<"*"<<x<<endl;
            vissmall.push(x);
            //cout<<"**"<<vissmall.top()<<endl;
            while(visbig.size()&&vissmall.top()<visbig.top())
            {
    
    
                int b=visbig.top(),s=vissmall.top();
                vissmall.pop();
                visbig.pop();
                vissmall.push(b);
                visbig.push(s);
            }
            //cout<<"****"<<vissmall.top()<<endl;
            while(vissmall.size()>visbig.size()+1)
            {
    
    
                visbig.push(vissmall.top());
                vissmall.pop();
            }
            //cout<<"******"<<vissmall.top()<<endl;
            if(i%2==1) 
            {
    
    
                num++;
                if(num%10==0) printf("%d\n",vissmall.top());
                else printf("%d ",vissmall.top());
            }
        }
        if(num%10) cout<<endl;
    }
    return 0;
}

Ultra-QuickSort

这道题就是一个求逆序数
这里用树状数组解决
注意这里的数据范围,需要使用一个离散化来将数据范围缩小

这里给一张图来说明一下我的离散化过程
这里需要注意的有两点:
1、离散化中不能出现0,不然sum操作会死循环
2、这里给出的数据可能是有重复元素的,不能去重
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5 * 1e5 + 10;
int n;
int a[N];
int tr[N];
struct node
{
    
    
    int x;
    int l;
} temp[N];
int cmp(const node &a, const node &b)
{
    
    
    return a.x < b.x;
}
int lowbit(int x)
{
    
    
    return x & -x;
}
void add(int x, int c)
{
    
    
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}
int sum(int x)
{
    
    
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
        res += tr[i];
    return res;
}
int main()
{
    
    
    while (cin >> n && n)
    {
    
    
        memset(tr,0,sizeof(tr));
        for (int i = 1; i <= n; i++)
        {
    
    
            scanf("%d", &temp[i].x);
            temp[i].l = i;
        }
        sort(temp+1,temp+n+1,cmp);
        for(int i=1;i<=n;i++)
            a[temp[i].l]=i;
        ll res = 0;
        for (int i = 1; i <= n; i++)
        {
    
    
            res += sum(n)-sum(a[i]);
            add(a[i], 1);
        }
        cout << res << endl;
    }
    return 0;
}

激光炸弹

这道题就是二维数组前缀和的模板题
唯一要注意的地方就是这里的边界问题,就是题目中的变量n和m

#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
int n, m, r, cnt;
int s[N][N];
int main()
{
    
    
    cin >> cnt >> r;
    r = min(5001, r);
    n = m = r;
    while (cnt--)
    {
    
    
        int x, y, w;
        cin >> x >> y >> w;
        x++, y++;
        n = max(n, x);
        m = max(m, y);
        s[x][y] += w;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    int res = 0;
    for (int i = r; i <= n; i++)
        for (int j = r; j <= m; j++)
            res = max(res, s[i][j] - s[i - r][j] - s[i][j - r] + s[i - r][j - r]);
    cout<<res<<endl;
    return 0;
}

货仓选址

对于这道题目有结论:
先对这个数组进行排序
1、当n是奇数的时候,选择中间点
2、当n是偶数的时候,选择中间两个点直接的任何一点均可

对于这个结论的证明是利用绝对值不等式
|x-a|+|x-b|>=|a-b|
我们利用每次去首位一组点用上式,就可以得到上述结论

有了结论之后写代码就很简单了

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N];
int main()
{
    
    
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    sort(a,a+n);
    int res=0;
    for(int i=0;i<n;i++) res=res+abs(a[i]-a[n/2]);
    cout<<res<<endl;
    return 0;
}

Tallest Cow(最高的牛)

这道题是一个差分的典型应用
每一对能相互看见的牛之间的所有的牛都一定比他们都矮!
因此我们可以将所有牛都初始化成最高的牛,每给定一对能相互看见的牛就对他们之间所有的牛的身高d都减1
这样这个问题就转化成了给定区间,对区间内数的操作,就是差分!

注意!这里题中给定的能相互看见的一对牛可能会有重复的!这里需要判重!

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
const int N=1e4+10;
int high[N];
int main()
{
    
    
    int n,m,h,p;
    cin>>n>>p>>h>>m;
    high[1]=h;
    set<PII>vis;
    for(int i=0;i<m;i++)
    {
    
    
        int a,b;
        cin>>a>>b;
        if(a>b) swap(a,b);
        if(!vis.count({
    
    a,b}))
        {
    
    
            vis.insert({
    
    a,b});
            high[a+1]--,high[b]++;
        }
    }
    for(int i=1;i<=n;i++)
    {
    
    
        high[i]=high[i]+high[i-1];
        cout<<high[i]<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_46126537/article/details/112715192