P14262 [ROI 2015 Day1] 自动好友
写了一个很另类的容斥。。。比其他dalao的做法复杂不少
(为了方便描述,如果 \(i,j\) 是一对潜在好友,我们就称 \((i,j)\) 是一对朋友对)
(以下的 \(a,b,c\) 均指题目中的三个属性)
首先我们可以枚举两个人并判断它们能否构成一组潜在好友。时间复杂度 \(O(n^2)\)。
然后,我们注意到这个题值域很小。于是本人就想着,依次算出 \(a\) 相同而 \(b,c\) 不同的朋友对, \(b\) 相同而 \(a,c\) 不同的朋友对,以及 \(c\) 相同而 \(a,b\) 不同的朋友对。最终答案就是三个朋友对的个数相加(显然不会有重复统计的朋友对)。
求这三种朋友对的方法都差不多。下面我们展开说说如何求 \(a\) 相同而 \(b,c\) 不同的朋友对。(其余同理)
我们可以将原来的人按 \(a\) 属性从小到大排序,这样 \(a\) 相同的人一定是在一个连续区间内的。
如果本题只有两个属性(仍然要求只有一个属性相同),那我们可以考虑记录一下当前 \(a\) 相同的人的左端点编号 \(l\) ,以及一个桶 \(mpb_{i}\) ,记录a属性的值落在这个区间的基础上,b属性为i的人有几个。
这样,我们从左往右遍历 \(i\) ,\(i\) 对答案的贡献就是 \(i-l-mpb_{b_{i}}\)。
现在变成三个属性了,怎么办?这个时候容斥就闪亮登场了。
沿用刚才的思路,我们用三个桶,一个桶 \(mpb_{i}\) ,记录a属性的值落在这个区间的基础上,b属性为i的人有几个;一个桶 \(mpc_{j}\) ,记录a属性的值落在这个区间的基础上,c属性为j的人有几个;一个桶 \(mp_{i,j}\) ,记录a属性的值落在这个区间的基础上,b属性为i的人且c属性为j的人有几个。
这样, \(i\) 前面 \(a\) 相同但不满足条件的人的数量 \(num\) 就是 \(mpc_{c{i}}+mpb_{b{i}}-mp_{b_{i},c_{i}}\) , \(i\) 对答案的贡献就是 \(i-top-num\) 。
这样我们就能求 \(a\) 相同而 \(b,c\) 不同的朋友对了。同理处理另两种情况即可。
代码实现的时候注意,如果进入了一个新的区间,那么 \(l\) 应当重新赋值,且 你开的几个桶都要初始化为 0 。
\(mp\) 这个桶如果不开 \(a\) 属性的这一位,那每次进入新区间都要初始化,很容易 TLE 。这里我采用了一个空间换时间的策略,给 \(mp\) 数组新开 \(a\) 的一维,这样我们只需要在处理一个大情况前初始化即可。
代码:
P14262正解
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else write(x/10),putchar(x%10+'0');
}
const int N=1e5+5;
const int M=105;
int n,ans,mp1[M],mp2[M],mp3[M],mp[M][M][M];
//mp1,mp2,mp3:等同于题解中的mpa,mpb,mpc
struct sw{
int b[4];
}a[N];
inline bool cmp1(sw x,sw y){
return x.b[1]<y.b[1];
}
inline bool cmp2(sw x,sw y){
return x.b[2]<y.b[2];
}
inline bool cmp3(sw x,sw y){
return x.b[3]<y.b[3];
}
inline void solve(int id){
//mp桶的初始化
for(int i=1;i<=100;i++){
for(int j=1;j<=100;j++){
for(int k=1;k<=100;k++){
mp[i][j][k]=0;
}
}
}
if(id==1){
int top=0;
//top:等同于题解中的l
sort(a+1,a+n+1,cmp1);
for(int i=1;i<=n;i++){
int x=a[i].b[1],y=a[i].b[2],z=a[i].b[3];
if(a[i].b[id]!=a[i-1].b[id]){
//不相同则进入新区间
for(int j=1;j<=100;j++){
mp2[j]=mp3[j]=0;
}
top=i;//记录一下左端点
}
else{
int x=a[i].b[1],y=a[i].b[2],z=a[i].b[3];
//桶题解
int num=mp2[y]+mp3[z]-mp[x][y][z];
ans=ans+(i-top-num);
}
//更新一下桶
mp2[y]++;mp3[z]++;
mp[x][y][z]++;
}
}
if(id==2){//注释见上
int top=0;
sort(a+1,a+n+1,cmp2);
for(int i=1;i<=n;i++){
int x=a[i].b[1],y=a[i].b[2],z=a[i].b[3];
if(a[i].b[id]!=a[i-1].b[id]){
for(int j=1;j<=100;j++){
mp1[j]=mp3[j]=0;
}
top=i;
}
else{
int num=mp1[x]+mp3[z]-mp[x][y][z];
ans=ans+(i-top-num);
}
mp1[x]++;mp3[z]++;
mp[x][y][z]++;
}
}
if(id==3){//注释见上
int top=0;
sort(a+1,a+n+1,cmp3);
for(int i=1;i<=n;i++){
int x=a[i].b[1],y=a[i].b[2],z=a[i].b[3];
if(a[i].b[id]!=a[i-1].b[id]){
for(int j=1;j<=100;j++){
mp2[j]=mp1[j]=0;
}
top=i;
}
else{
int x=a[i].b[1],y=a[i].b[2],z=a[i].b[3];
int num=mp2[y]+mp1[x]-mp[x][y][z];
ans=ans+(i-top-num);
}
mp2[y]++;mp1[x]++;
mp[x][y][z]++;
}
}
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
a[i].b[1]=read(),a[i].b[2]=read(),a[i].b[3]=read();
}
solve(1);solve(2);solve(3);
printf("%lld",ans);
return 0;
}
另,如果你有对拍的需求,也欢迎你用如下代码与你的正解对拍(即 \(O(n^2)\) 代码):
P14262暴力
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else write(x/10),putchar(x%10+'0');
}
const int N=1e5+5;
int n,a[N],b[N],c[N];
signed main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read(),b[i]=read(),c[i]=read();
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if((a[i]==a[j]&&b[i]!=b[j]&&c[i]!=c[j])||(a[i]!=a[j]&&b[i]==b[j]&&c[i]!=c[j])||(a[i]!=a[j]&&b[i]!=b[j]&&c[i]==c[j])){
ans++;
}
}
}
printf("%lld",ans);
return 0;
}
如果你觉得这篇题解还不错的话,记得留下你的赞呀qwq

浙公网安备 33010602011771号