cf1566 Global Round 16-D1【树状数组】、D2【离散化+模拟】

Date:2022.01.17
题意: n ∗ m n*m nm个座, n ∗ m n*m nm个人。每个人都有近视度,近视度小的坐的座位下标靠前,因此相对位置固定。每个人要找到他所在的行,并进入所在的列,在此过程中,定义这个人的“不方便程度”为他在这一行往里走的过程中,经过了多少个比他下标小的人。求总的不方便度最小是多少。
D1: n = 1 n=1 n=1,其它同D2。
D2:

在这里插入图片描述
思路①:D1只有一行,贪心【贪心见下】放进去,暴力(不如练练BIT板子)。
代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 310;
typedef long long LL;
LL t,n,m,k;
LL a[N],tr[N];
struct node
{
    LL zhi,idx;
}s[N];
bool cmp(node a,node b)
{
    if(a.zhi==b.zhi) return a.idx>b.idx;
    return a.zhi<b.zhi;
}
LL lowbit(LL x)
{
    return x&-x;
}
void add(LL x,LL c)
{
    for(int i=x;i<=m;i+=lowbit(i)) tr[i]+=c;
}
LL sum(LL x)
{
    LL res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>t;
    while(t--)
    {
        memset(tr,0,sizeof tr);
        cin>>n>>m;
        for(int i=1;i<=m;i++) {cin>>s[i].zhi;s[i].idx=i;}
        sort(s+1,s+1+m,cmp);
        LL res=0;
        for(int i=1;i<=m;i++)
        {
            res+=sum(s[i].idx);
            add(s[i].idx,1);
        }
        cout<<res<<endl;
    }
    return 0;
}

思路②:D2有 n ∗ m n*m nm人,我们先来考虑如果我们得出第 1 1 1 ~ n ∗ m n*m nm个人应该在哪个座位上,我们能不能求出来总的正序对个数? n ∈ 300 , m ∈ 300 n\in300,m\in300 n300,m300 O ( n 3 ) O(n^3) O(n3)枚举每个元素之前有多少比它小的,再累加即可求得。
因此考虑如何找到每个人该坐在哪个位置,不同于D1,D2可能存在相同近视度的人分别处于一行尾部和另一行头部的形式,模拟放入。我开了一个 q u e u e queue queue类型的数组, q u e u e [ i ] queue[i] queue[i]中含所有近视度为i的人的下标,近视度本身可能很大,我们直接开 q u e u e [ i ] queue[i] queue[i]可能会开到 1 0 9 10^9 109这不现实。我们只需要用到近视度的相对关系,因此离散化, q u e u e queue queue最大也只会开 n ∗ m n*m nm项,所有 q u e u e queue queue中所有项不会超过人的上限 n ∗ m n*m nm,因此全遍历完所有 q u e u e queue queue时间是允许的。
剩下就模拟+贪心,不难发现值相同情况下能先往里坐就先往里坐,这样能保证后进来的、且下标较大的元素相对位置靠前,也就不会经过下标比他小的元素。此外,放人的顺序应按近视度由小到大放,放完一个近视度的所有人才能放更大近视度的人。
q u e u e queue queue清空无法 c l e a r ( ) clear() clear(),因此每次暴力弹出所有元素。
代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 310;
typedef long long LL;
LL t,n,m,k;
LL c[N][N];
LL a[N*N],b[N*N]; 
queue<LL>q[N*N];
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>t;
    while(t--)
    {
        for(int i=1;i<=300*300;i++)
            while (!q[i].empty()) q[i].pop();
        LL maxx=0;
        cin>>n>>m;
        for(int i=0;i<n*m;i++) {cin>>a[i];b[i]=a[i];}
        sort(b,b+n*m);
        LL x=unique(b,b+n*m)-b;
        for(int i=0;i<n*m;i++) 
        {
            a[i]=lower_bound(b,b+x,a[i])-b+1;
            maxx=max(maxx,a[i]);
            q[a[i]].push(i+1);
        }
        LL row=0,zhi=1;
        LL sy=m,dq=q[1].size();
        for(int i=1;i<=n&&zhi<=maxx;)
        {
            if(dq>=sy) 
            {
                dq-=sy;
                for(int j=m;j>=m-sy+1;j--) {c[i][j]=q[zhi].front();q[zhi].pop();}
                sy=0;
            }
            else if(dq<sy)
            {
                for(int j=m-sy+dq;j>=m-sy+1;j--) {c[i][j]=q[zhi].front();q[zhi].pop();}
                sy-=dq;
                dq=0;
            }
            //cout<<dq<<' '<<sy<<endl;
            if(sy==0) {i++;sy=m;}
            if(dq==0) {zhi++;dq=q[zhi].size();}
        }
        LL ans=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                for(int k=1;k<j;k++)
                    if(c[i][k]<c[i][j]) ans++;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值