题目描述
七夕节因牛郎织女的传说而被扣上了「情人节」的帽子。
于是TYVJ今年举办了一次线下七夕祭。
Vani同学今年成功邀请到了cl同学陪他来共度七夕,于是他们决定去TYVJ七夕祭游玩。
TYVJ七夕祭和11区的夏祭的形式很像。
矩形的祭典会场由N排M列共计N×M个摊点组成。
虽然摊点种类繁多,不过cl只对其中的一部分摊点感兴趣,比如章鱼烧、苹果糖、棉花糖、射的屋……什么的。
Vani预先联系了七夕祭的负责人zhq,希望能够通过恰当地布置会场,使得各行中cl感兴趣的摊点数一样多,并且各列中cl感兴趣的摊点数也一样多。
不过zhq告诉Vani,摊点已经随意布置完毕了,如果想满足cl的要求,唯一的调整方式就是交换两个相邻的摊点。
两个摊点相邻,当且仅当他们处在同一行或者同一列的相邻位置上。
由于zhq率领的TYVJ开发小组成功地扭曲了空间,每一行或每一列的第一个位置和最后一个位置也算作相邻。
现在Vani想知道他的两个要求最多能满足多少个。
在此前提下,至少需要交换多少次摊点。
输入格式
第一行包含三个整数N和M和T,T表示cl对多少个摊点感兴趣。
接下来T行,每行两个整数x, y,表示cl对处在第x行第y列的摊点感兴趣。
输出格式
首先输出一个字符串。
如果能满足Vani的全部两个要求,输出both;
如果通过调整只能使得各行中cl感兴趣的摊点数一样多,输出row;
如果只能使各列中cl感兴趣的摊点数一样多,输出column;
如果均不能满足,输出impossible。
如果输出的字符串不是impossible, 接下来输出最小交换次数,与字符串之间用一个空格隔开。
数据范围
$1≤N,M≤100000,
0≤T≤min(N∗M,100000),
1≤x≤N,
1≤y≤M$
样例
输入样例:
2 3 4
1 3
2 1
2 2
2 3
输出样例:
row 1
分治+贪心+前缀和+中位数+排序
这道题目有一个非常重要的性质就是,只会改变相邻的两个数的位置,因此我们交换两个数,只会改变一行的喜爱小摊或者一列的喜爱小摊,而不会同时改变行和列的喜爱小摊,既然这样的话,我们就可以将这道题目分成两个部分,一部分是求行的最少次数,一部分是求列的最少次数。
既然如此的话,这道题目就成为了环形的均分纸牌问题,均分纸牌这是一道经典的贪心问题,可以自行百度理解即可懒惰病发作ing 但是环形均分纸牌问题和普通均分纸牌问题又有不同之处,因此我们要截环为序列,所以说我们可以利用中位数把环形变成区间,具体思路可见《算法竞赛进阶指南》。
感谢@sandychn 大佬指出简短代码中一个小问题.
后更新简短代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define fir(i,a,b) for(ll i=a;i<=b;i++)
const int N=1e5+10;
ll n,m,t,a[N],b[N],f[N],x,y,rr,cc;
ll calc(ll a[],ll n)
{
fir(i,1,n)
{
a[i]-=(a[0]/n);
f[i]=f[i-1]+a[i];
}
sort(f+1,f+1+n);
ll mid=(n+1)>>1,ans=0;//不是n|1,因为n若为奇数会出问题.不过这道题目也不会出现问题.因为中位数只要求是最中间,但是如果说我们要求出n+1的话,那么不能用n|1. 感谢@sandychn 大佬指出问题.
fir(i,1,n)
ans+=abs(f[mid]-f[i]);
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m>>t;
for(int i=1;i<=t;i++)
{
cin>>x>>y;
a[x]++;
b[y]++;
}
fir(i,1,n)
a[0]+=a[i];
fir(i,1,m)
b[0]+=b[i];
ll as=a[0]%n,bs=b[0]%m;
if (!as && !bs)
cout<<"both "<<calc(a,n)+calc(b,m);
else if(!as)
cout<<"row "<<calc(a,n);
else if(!bs)
cout<<"column "<<calc(b,m);
else
cout<<"impossible";
return 0;
}
lyd老师的精简C++ 代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int u=100010;
long long b[u],c[u],f[u];
long long n,m,t,i,j,x,y;
long long calc(long long a[u],int n)
{
long long ans=0; int i;
for(i=1;i<=n;i++)
{
a[i]-=a[0]/n;
f[i]=f[i-1]+a[i];
}
sort(f+1,f+n+1);
for(i=1;i<=n;i++) ans+=abs(f[i]-f[n+1>>1]);
return ans;
}
int main()
{
freopen("tanabata.in","r",stdin);
freopen("tanabata.out","w",stdout);
cin>>n>>m>>t;
for(i=1;i<=t;i++)
{
scanf("%d%d",&x,&y);
b[x]++,c[y]++;
}
for(i=1;i<=n;i++) b[0]+=b[i];
for(i=1;i<=m;i++) c[0]+=c[i];
if(b[0]%n==0&&c[0]%m==0)
printf("both %lld\n",calc(b,n)+calc(c,m));
else if(b[0]%n==0)
printf("row %lld\n",calc(b,n));
else if(c[0]%m==0)
printf("column %lld\n",calc(c,m));
else puts("impossible");
return 0;
}
蒟蒻我的繁杂代码
#include <bits/stdc++.h>
using namespace std;
const int N=100100;
#define ll long long
ll n,m,t,i,j,k,s[N],a[N],x,y,ans1,ans2,r[N],c[N],rr,cc;
void init()
{
ios::sync_with_stdio(false);
cin>>n>>m>>t;
for (i=1;i<=t;i++)
{
int x,y;
cin>>x>>y;
r[x]++;
c[y]++;
rr++;
cc++;
}
}
bool work1()//横排
{
if (t%n!=0)
return 0;
memset(s,0,sizeof(s));
rr/=n;
for (i=1;i<=n;i++)
{
r[i]-=rr;
s[i]=s[i-1]+r[i];
}
sort(s+1,s+1+n);
int k=(1+n)>>1;
for (i=1;i<=n;i++)
ans1+=abs(s[i]-s[k]);
return 1;
}
bool work2()//竖排
{
if (t%m!=0)
return 0;
memset(s,0,sizeof(s));
cc/=m;
for (i=1;i<=m;i++)
{
c[i]-=cc;
s[i]=s[i-1]+c[i];
}
sort(s+1,s+1+m);
int k=(1+m)>>1;
for (i=1;i<=m;i++)
ans2+=abs(s[i]-s[k]);
return 1;
}
int main()
{
init();
bool ok1=work1(),ok2=work2();
if (ok1 && ok2)
cout<<"both";
else
if (ok1)
cout<<"row";
else if (ok2)
cout<<"column";
else
cout<<"impossible";
if (ok1 || ok2)
cout<<" "<<ans1+ans2;
return 0;
}
就离谱, 懒得翻书直接题解, 你跟我说具体思路可见《算法竞赛进阶指南》??
那我要你这题解何用?? ctrl + c . ctrl + v ???
就离谱,学OI还想偷懒?你干脆退役区安享晚年吧??
挑刺挑刺,你妈就是刺。
真的,人家辛辛苦苦打了半天,你一句话人家的心血都付诸东流了,锤子长在脑壳上,我都奇怪世博会怎么没喊你去展览。。。
政治没学过的家伙。。。
你觉得你学过是吗,就凭你这句话
属于那种学了点屁事就出来卖弄的,难听点就是没有搞清楚政治这句话适用的范围,不看你运用的场合。
不好意思,当时有些幼稚,语气很激烈,非常抱歉。
…
lj人
虽然这位老哥的评论很nt,但是这题推荐看他的题解
可他说的是实话吧,题解本就应该解决人们的问题,如果只是将代码罗列出来并没有仔细解释每一步骤,为什么叫题解呢....虽然说的有点激烈hhhh,但对于这件事情还是在理的,只不过表达再转变下就更好了hhhh
$lyd$ 是指老 $y$ 的吗
$qwq$这个题解写的不错,包括了许多不同风格的代码,思路清楚
请问大佬为什么要排f这个前缀和数组啊
请问怎么一开始就知道要用
long long的 我实在是没看出来
Orz
繁 杂
对于calc函数可以看122糖果传递
每行的总和和每列的总和不是t吗?
dl,我对题目理解有个问题, 1摊位和2摊位相邻, 2摊位还和3摊位相邻, 那么1号摊位和2号摊位交换了位置之后,能否继续和3摊位继续交换位置?
%%%秦老师
n已经定义成全局变量了啊
怎么calc函数里又定义了n
不会重名吗???
不会的,函数内部的局部变量等级高于全局变量
谢谢大佬
您客气了
您客气了
客气啥
想问一下,如果最后不是abs(s[i]-s[k])求和,而是ans把前一半s[i]减去,后一半s[i]加上,得到的效果不是一样的吗?
为啥是一样的。。。
我一脸蒙圈。。。。
这个循环是。
这个是绝对值求和,您哪个s[i]减去,加去是什么意思?我太菜了,根本看不懂,您说的。。。。
for (int i=1; i<=l/2; i)
ans-=s[i];
for (int i=l/2+1+(l&1); i<=l; i)
ans+=s[i];
哦哦,我是这个意思,刚又交了一遍,居然过了。。。打扰大佬了
QwQ,木有事情啦。
为什么这三份代码都又统计了一遍a[0],b[0],不是一定都是T吗
这三份代码都是AC代码的....
为什么会TLE呢?
时间复杂度不是挺好的吗?
额 , 我的意思是 题目中给的数据 T
已经给了T(感兴趣的摊位)了 ,为啥还要在统计一遍
我们计算每一行,每一列感兴趣的摊位,然后进行处理.
可是它就是等于T的,我试了直接用T ,也过了,是测试数据的事吗?
数据水了吧…
我记得有无解这回事.
dalao 我想问一下为啥这题在计算那个mid的时候不能直接整除2,还是不太明白
因为避免奇数整除2,比如说3/2=1,但是我们要的中位数是2,所以(3+1)/2=2,而假如是4,(4+1)/2=2,依旧是2.
嗷嗷 懂了懂了 谢谢dalao
这里有一个疑问就是最后的答案是从 i =1一直累加到M的前缀和,但是我的想法是 第一个数和第二个数交换后,即第一个前缀和第二个数交换。如果直接在用第二个前缀(即第一个数和第二个数的和)与第三个数进行交换,会不会重复了啦,是否应该把第一个数更新为T/M后在和第二个数相加变成第二个前缀呢?
这里的前缀和,实际上是在求贪心经典模型均分纸牌和中位数性质,而并不是原题目的交换.这一点要切记,我们最后得到的答案,是环形均分纸牌+中位数性质.这一点,书上写得很明确,我的题解也是按照这个思路的.
calc函数,不是求原问题,而是在求原问题转换后两个模型的答案.
简短代码的
calc
函数中$mid=(n|1)>>1$不对怎么了,不就是(n+1)/2吗.这些代码都是我提交AC的.
位运算中n|1相当于n+1
我知道你的意思。当$n$为奇数时$n|1==n$
$n$为奇数时,$(n|1)>>1=n>>1=n/2$不对。代码会WA。只是发现有错误,想跟你说一下。
首先感谢一下,然后我想想啊.
刚才编辑了代码,发现果然是这这样的,感谢!!!
没事。这道题直接写(n|1)>>1会WA掉acwing上的数据。奇数应取(n+1)/2,偶数取n/2或(n+1)/2都可以(除法向下取整)。所以(n+1)/2对奇偶都适用。n|1应该只适用于偶数呀。
WA了?我记得我是AC的,算了不用管这个BUG,已经修改代码了.
好嘞
哪里看到李煜东老师代码的?
书上有光盘,光盘上有的啊.
谢谢,我去看看
不谢
没有光盘的可以去GitHub上找,搜书名字就行了。