蓝书(算法竞赛进阶指南)刷题记录——CH0502 & BZOJ3032 七夕祭(前缀和+贪心)

题目:CH0502/BZOJ3032.
题目大意:给出一个 n ∗ m n*m nm的矩阵,以及一个 t t t个选定点,现在这所有点都可以与相邻的点交换,且边界也可以与另一条边界交换,现在问能否是每行和每列的选定点的数目相同,以及最少要多少步.
1 ≤ n , m ≤ 1 0 5 , 1 ≤ t ≤ m i n ( n ∗ m , 1 0 5 ) 1\leq n,m\leq 10^5,1\leq t\leq min(n*m,10^5) 1n,m105,1tmin(nm,105).

很显然地,可以发现行列之间互相不影响,所以我们先把两个问题拆开来,接下来我们只讨论列,行同理.

然后我们发现这个问题貌似跟均分纸牌有点联系,建议先去做一下.

我们从均分纸牌这道题可以推出,其实无解的情况就是列数无法整除总点数.

现在记 c [ i ] c[i] c[i]为第 i i i列的点数,现在我们按照均分纸牌的方式推,我们假设 c [ 1 ]   c [ m ] c[1]~c[m] c[1] c[m]的平均数为 x x x,设 C [ i ] C[i] C[i] c [ i ] c[i] c[i]的前缀和,那么最少移动步数为 ∑ i = 1 m ∣ i ∗ x − C [ i ] ∣ \sum_{i=1}^{m}|i*x-C[i]| i=1mixC[i].

a [ i ] = c [ i ] − x a[i]=c[i]-x a[i]=c[i]x A [ i ] A[i] A[i] a [ i ] a[i] a[i]的前缀和,那么原式等价于 ∑ i = 1 m A [ i ] \sum_{i=1}^{m}A[i] i=1mA[i].

现在我们继续使用均分纸牌这个思想,我们发现其实头尾相邻这个问题其实就是转化为一个环状均分纸牌问题.

考虑在第 k k k个点开始计算,答案会变成 ∑ i = k m ∣ A [ k ] − A [ k ] ∣ + ∑ i = 1 k − 1 ∣ A [ i ] + A [ m ] − A [ k ] ∣ \sum_{i=k}^{m}|A[k]-A[k]|+\sum_{i=1}^{k-1}|A[i]+A[m]-A[k]| i=kmA[k]A[k]+i=1k1A[i]+A[m]A[k].

我们发现,问题若有解则一定有 A [ m ] = 0 A[m]=0 A[m]=0,所以把式子转化成 ∑ i = 1 m ∣ A [ i ] − A [ k ] ∣ \sum_{i=1}^{m}|A[i]-A[k]| i=1mA[i]A[k],发现这个式子跟货仓选址很像,所以我们也取中位数来做.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000;
int n,m,t;
LL sumx,sumy,sx[N+9],sy[N+9],ansx,ansy,x[N+9],y[N+9];
bool flag0=0,flag1=0;;
Abigail into(){
  scanf("%d%d%d",&n,&m,&t);
  int u,v;
  for (int i=1;i<=t;i++){
    scanf("%d%d",&u,&v);
    x[u]++;y[v]++;
  }
}
Abigail work(){
  for (int i=1;i<=n;i++)
    sx[i]=sx[i-1]+x[i];
  for (int i=1;i<=m;i++)
    sy[i]=sy[i-1]+y[i];
  if (t%n==0) flag0=1;
  if (t%m==0) flag1=1;
  sumx=t/n,sumy=t/m;
  for (int i=1;i<=n;i++)
    sx[i]-=sumx*i;
  for (int i=1;i<=m;i++)
    sy[i]-=sumy*i;
  sort(sx+1,sx+1+n);
  sort(sy+1,sy+1+m);
  for (int i=1;i<=n;i++)
    ansx+=abs(sx[i]-sx[1+n>>1]);
  for (int i=1;i<=m;i++)
    ansy+=abs(sy[i]-sy[1+m>>1]);
}
Abigail outo(){
  if (flag0){
    if (flag1) printf("both %lld\n",ansx+ansy);
    else printf("row %lld\n",ansx);
  }else{
    if (flag1) printf("column %lld\n",ansy);
    else printf("impossible\n");
  }
}
int main(){
  int T=1;
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值