题目传送门
我的博客-欢迎光顾
写了一个很另类的容斥。。。比其他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