结合官方榜与自己做题体验,按难度做题顺序:I -> J -> E -> C -> D -> G -> A -> L -> H -> B -> F -> K
H题和L题待补。
官方题解下载(免费):https://download.csdn.net/download/Omigeq/86500801?spm=1001.2014.3001.5503
目录
A - Segment Tree
题目代码的bug在于,a的空间只开了4*n,但每次却要访问a[ls]和a[rs],在l==r的结点可能产生越界。
我们模拟建树过程,每次用now<<1 >= n<<2 || (now<<1|1) >= n>>2判断是否越界。
递归时新增一个参数lr,判断是亲结点的左孩子还是右孩子。
询问的区间可能有tot = n*(n+1)/2个,我们用ovf变量记录越界的区间数,对于越界的结点,若是亲结点的左孩子,则ovf+=l;若是右孩子则ovf+=n-l+1。
然而可能会有重复计算的区间,根据我们访问到l==r的结点的的l是单调递增的,我们用cnt记录[1,l],[2,l],[3,l],...,[l-1,l],[l,l]中出现过的区间数,每次访问越界右孩子则cnt++(因为访问越界右孩子计算了区间[l,l],[l,l+1],...,[l,n-1],[l,n],后面访问的越界左孩子必会重复计算),而后面访问的越界左孩子计算ovf时要减去cnt。
模拟建树得到ovf,则每次访问不出错的概率为(tot-ovf)/tot,q次访问不出错的概率为((tot-ovf)/tot)^q,答案为
逆元不解释。
#include<bits/stdc++.h>
#define ll long long
#define MOD (ll)(1e9+7)
using namespace std;
int T;
ll n,q,ans,ovf,cnt;
void build(ll now,ll l,ll r,bool lr){ // lr: 0 left, 1 right
if (l==r){
if (now<<1 >= n<<2 || (now<<1|1) >= n<<2){
if (lr){
ovf += n-l+1;
++cnt;
}else ovf += l-cnt;
}
return;
}
int mid = (l+r)>>1;
build(now<<1,l,mid,0);
build(now<<1|1,mid+1,r,1);
return;
}
inline ll fpm(ll a,ll p){
a %= MOD;
ll res = 1;
while (p){
if (p&1) res = res*a%MOD;
a = a*a%MOD;
p >>= 1;
}
return res;
}
inline void solve(){
scanf("%d",&T);
ans = 1;
while (T--){
scanf("%lld%lld",&n,&q);
ovf = cnt = 0;
build(1,1,n,0);
ans = ans*fpm((n*(n+1)/2-ovf)%MOD*fpm(n*(n+1)/2,MOD-2),q)%MOD;
}
printf("%lld\n",ans);
}
int main(){
solve();
return 0;
}
B - Cell
题意可转化为:在(n,m)放一个细胞,经过k秒后,(0,0)~(n,m)矩形范围内细胞总个数。
再进一步转化为:从(n,m)开始,走k步之后,位于(0,0)~(n,m)矩形范围内方案总个数。
设横向走了x步,纵向走了k-x步,则答案
其中ansx[x]表示横向走x步、终点位于[0,n]范围内方案数。
设向左走了a步,向右走了x-a步,
要求终点>=0 ,
要求终点<=n ,
则 。
由公式(杨辉三角),可以将ansx[x]乘二、处理一下首末项得到ansx[x+1],于是ansx[x]可以O(1)推到ansx[x+1],于是可以预处理出ansx[x]。
同理 ,也可以预处理。
所以最终答案 ,能在线性时间复杂度算出来。
#include<bits/stdc++.h>
#define ll long long
#define MOD 998244353LL
using namespace std;
int T;
ll n,m,k,fac[100010],inv[100010],invf[100010],ansx[100010],ansy[100010];
inline ll comb(ll u, ll d){
if (u>d) return 0;
if (u==0||u==d) return 1;
return fac[d]*invf[u]%MOD*invf[d-u]%MOD;}
inline ll Ceil(ll u, ll d){return u%d==0?u/d:u/d+1;}
inline void getansx(){
ll llb,lub,lb,ub; // (last)_lower/upper_bound
ansx[0] = comb(0,0);
lb = 0, ub = 0;
for (ll x=1;x<=k;++x){
llb = lb, lub = ub;
lb = Ceil(x,2LL), ub = min(x,(n+x)>>1);
ansx[x] = (ansx[x-1]<<1)%MOD; // 乘二
ansx[x] = (ansx[x]-comb(llb,x-1)+MOD)%MOD; // 处理首项
if (lb == llb) ansx[x] = (ansx[x]+comb(lb,x))%MOD;
ansx[x] = (ansx[x]-comb(lub,x-1)+MOD)%MOD; // 处理末项
if (ub > lub) ansx[x] = (ansx[x]+comb(ub,x))%MOD;
}
}
inline void getansy(){ // 与getansx同理,复制后改一下就好
ll llb,lub,lb,ub; // (last)_lower/upper_bound
ansy[0] = comb(0,0);
lb = 0, ub = 0;
for (ll x=1;x<=k;++x){
llb = lb, lub = ub;
lb = Ceil(x,2LL), ub = min(x,(m+x)>>1);
ansy[x] = (ansy[x-1]<<1)%MOD; // 乘二
ansy[x] = (ansy[x]-comb(llb,x-1)+MOD)%MOD; // 处理首项
if (lb == llb) ansy[x] = (ansy[x]+comb(lb,x))%MOD;
ansy[x] = (ansy[x]-comb(lub,x-1)+MOD)%MOD; // 处理末项
if (ub > lub) ansy[x] = (ansy[x]+comb(ub,x))%MOD;
}
}
inline void solve(){
scanf("%lld%lld%lld",&n,&m,&k);
getansx(), getansy();
ll ans = 0;
for (ll x=0;x<=k;++x) ans = (ans+ansx[x]*ansy[k-x]%MOD*comb(x,k))%MOD;
printf("%lld\n",ans);
}
int main(){
fac[0] = fac[1] = inv[0] = inv[1] = invf[0] = invf[1] = 1;
for (int i=2;i<=100000;i++){
fac[i] = fac[i-1]*i%MOD;
inv[i] = (MOD-MOD/i)*inv[MOD%i]%MOD;
invf[i] = invf[i-1]*inv[i]%MOD;
}
scanf("%d",&T);
while (T--) solve();
return 0;
}
C - GCD
题意可转化为求 ,其中括号内的表达式为真时值为1,否则为0。
形式与数论分块经典问题相似,可利用数论分块解决。
以下代码中,x为分块右边界,让它遍历l和r分块的右边界的并集,每次若i符合条件,ans += x-i+1(分块区间长度)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll l,r,k;
inline void solve(){
scanf("%lld%lld%lld",&l,&r,&k);
--l;
ll ans = 0;
for (ll i=1,x=0;i<=r;i=x+1){
if (i<=l) x = min(l/(l/i),r/(r/i));
else x = r/(r/i);
if (r/i-l/i>=k) ans += x-i+1;
}
printf("%lld\n",ans);
}
int main(){
solve();
return 0;
}
D - Disease
只考虑向上层传染即可。
设f[i]为i点初始时得病概率,g[i]为i点最终的病概率,stp[i]为i点深度(原题的level),maxstp为树的深度,i点得病且传染的概率给父节点为 , 则
(其中a/b为j传染i的概率)
对于深度1,我们直接加g[1]到答案上。我们再枚举深度t = 2~maxstp,将答案 += t*P(t层至少存在一个得病且t层之上不得病)。
而 P(t层至少存在一个得病且t层之上不得病) = P(t层至少存在一个得病且不传染给上一层)*P(t层之上初始时都不得病) = (P(t层不"得病且传染")-P(t层完全不得病))*P(t层之上初始时都不得病)
又 P(t层不"得病且传染") = (这里a/b是指i传染给亲节点的概率)
P(t层完全不得病) =
P(t层之上初始时都不得病) =
所以答案
此外还有乘法逆元和负数取模(代码中getmod())这些常识,不会的赶紧去补一下。
#include<bits/stdc++.h>
#define ll long long
#define N 1000000
#define MOD (ll)(1e9+7)
using namespace std;
struct Edge{int v; ll a,b;};
vector<Edge> e[N+10];
ll n,f[N+10],g[N+10],stp[N+10],maxstp,prdf[N+10],prdg[N+10],prdabg[N+10];
ll getmod(ll x){return (x%MOD+MOD)%MOD;} // 负数取模
ll fpm(ll x, ll p){ // 快速幂求逆元
ll res = 1;
while (p){
if (p&1) res = getmod(res*x);
p >>= 1;
x = getmod(x*x);
}
return res;
}
void dfs(int now){
ll prd = 1;
for (Edge E: e[now]){
int j = E.v;
stp[j] = stp[now] + 1;
maxstp = max(maxstp,stp[j]);
dfs(j);
ll tmp = getmod(g[j]*getmod(E.a*fpm(E.b,MOD-2)));
prd = getmod(prd*getmod(1-tmp));
prdabg[stp[j]] = getmod(prdabg[stp[j]]*getmod(1-tmp));
}
g[now] = (f[now]+getmod(getmod(1-f[now])*getmod(1-prd)));
prdf[stp[now]] = getmod(prdf[stp[now]]*getmod(1-f[now]));
prdg[stp[now]] = getmod(prdg[stp[now]]*getmod(1-g[now]));
}
inline void solve(){
scanf("%lld\n",&n);
for (int i=1;i<=n;++i){
ll p,q;
scanf("%lld%lld",&p,&q);
f[i] = getmod(p*fpm(q,MOD-2));
prdf[i] = prdg[i] = prdabg[i] = 1;
}
for (int i=1;i<n;++i){
int u,v; ll a,b;
scanf("%d%d%lld%lld",&u,&v,&a,&b);
e[u].push_back(Edge{v,a,b});
}
maxstp = stp[1] = 1;
dfs(1);
ll ans = g[1];
for (int t=2;t<=maxstp;++t) prdf[t] = getmod(prdf[t]*prdf[t-1]); // 前缀积
for (int t=2;t<=maxstp;++t) ans = getmod(ans+getmod(t*getmod(getmod(prdabg[t]-prdg[t])*prdf[t-1])));
printf("%lld\n",ans);
}
int main(){
solve();
return 0;
}
E - Swapping Game
在草稿打表找规律,打到n秒时发现序列为n,n-1,...,3,2,1,与0秒的序列相反,易知再过n秒,序列又回到0秒时的样子,形成了一个循环。
除此之外,奇数先每秒递增1,撞到边界后变为递减;偶数先每秒递减1,撞到边界后变为递增。根据此规律即可O(1)算出数的位置。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int T,n,k,q;
inline void solve(){
scanf("%d%d%d",&n,&k,&q);
k %= n<<1;
bool rev = (k>=n);
k %= n;
int ans;
if (q&1){
if (k>n-q) ans = n+1-(k-n+q);
else ans = q+k;
}else{
if (k>=q) ans = k-q+1;
else ans = q-k;
}
printf("%d\n",rev?n+1-ans:ans);
}
int main(){
scanf("%d",&T);
while (T--) solve();
return 0;
}
F - Code
字符串长度总和不超过5000,可建一个大小不超过5000的字典树,用bfs暴搜。
我们从字典树根节点开始走,队列中每个元素代表走过的不同的前缀路线。
以下代码中,f[i][j][k]的值为步数,i和j分别代表两种走法目前所在结点a和b,k代表走法是否已经不同。队列用array<int,3>存放i,j,k的信息。nxt[i][j]记录字典树结点i的j方向子结点。ed[i]代表结点i是否为某个输入的串的串尾。
每次获取到队首元素,分别处理要走0还是1的情况,如果两个结点都能走该方向,就走一步。这一步有四种情况:a,b同时到达串尾,a到达串尾,b到达串尾,两点都没到达任何串尾。
第一种情况,如果已有不同走法即k=1可以直接返回输出答案了,否则不理。
第二三种情况,可以产生同一个前缀路线的不同走法,令k=1。
第四种情况,就简简单单走一下。
这里写了个update函数,如果f[i][j][k]以前没走过才update,否则不浪费再这么走一次。
如果最终没找到,答案就是-1。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef array<int,3> AR;
int n,nxt[5010][2],tot,f[5010][5010][2];
bool ed[5010];
char s[5010];
queue<AR> q;
inline bool update(int &x, int y){
if (x == -1){
x = y+1;
return true;
}
return false;
}
inline bool work(AR r, int k){
int a = nxt[r[0]][k], b = nxt[r[1]][k];
if (a && b){
int now = f[r[0]][r[1]][r[2]];
if (ed[a] && ed[b] && r[2] && update(f[0][0][1],now)) return true;
if (ed[a] && update(f[0][b][1],now)) q.push(AR{0,b,1});
if (ed[b] && update(f[a][0][1],now)) q.push(AR{a,0,1});
if (update(f[a][b][r[2]],now)) q.push(AR{a,b,r[2]});
}
return false;
}
inline void bfs(){
f[0][0][0] = 0;
q.push(AR{0,0,0});
while (!q.empty()){
AR now = q.front(); q.pop();
if (work(now,0)) return;
if (work(now,1)) return;
}
}
inline void buildTrie(){
int now = 0;
for (int j=0;s[j];++j){
s[j] -= '0';
if (!nxt[now][s[j]]) nxt[now][s[j]] = ++tot;
now = nxt[now][s[j]];
}
ed[now] = true;
}
inline void solve(){
memset(nxt,0,sizeof(nxt));
memset(ed,0,sizeof(ed));
memset(f,-1,sizeof(f));
tot = 0;
scanf("%d",&n);
for (int i=0;i<n;++i){
scanf("%s",s);
buildTrie();
}
bfs();
printf("%d\n",f[0][0][1]);
}
int main(){
solve();
return 0;
}
G - Interesting Set
这道题是我唯一想不通的玄学题目。
n<=6和n为偶数时无解。
n<=6的情况就不推了,自己在草稿推一推应该能推出来。
设原集合为S,S的元素和为s。
我们知道要把S删去一个元素后分割为两个相等的集合,必须满足 。
n是偶数时,若S中全是偶数,将所有ai不断除以2可以得到等价的非全偶数解;若S中全是奇数,s则为偶数,删去任何一个ai,s为减为奇数,不满足 ;只剩下S中又有奇数又有偶数的情况,然而由 知任何一个ai奇偶性与s相同。三种情况都不行,则n不可能为偶数。
对于n>=7且n为奇数时,总能找到解,解的形式与样例相似,即前n个奇数构成的集合。
这种情况下,首先s肯定为奇数,满足 ,实际上我们可以算出s=n*n,将S-ai分割为和相等的两子集,单个子集元素和为q,q可能为奇数也可能为偶数。
因为最小元素为1,最大元素为n*2-1,则q可能取值为(n*n-1)/2-n,(n*n-1)/2-n+1,...,(n*n-1)/2。
q为奇数时,q可能取值为(n*n-1)/2-n,(n*n-1)/2-n+2,...,我们拿掉1使它变为偶数的情况。
q为偶数时,q可能取值为(n*n+1)/2-n,(n*n+1)/2-n+2,...,其中一个子集可取(n-1)/2个元素,我们发现q每个可能取值之间相差2。因此,对于q最大的那个情况,我们贪心能选的最大的数,构造出一个长度为(n-1)/2的所谓“最大初始子集合”Q;接下来每次对于q小2的情况,从Q中找到最小的能减2的数减2,一直到q最小总是可以找到。说得自己都晕了,没必要看,其实就是dfs。
不用想这么多,我也懒得想了,本题数据范围可以直接O(n^2)的暴力dfs完事。
dfs两个参数,id表示在选择答案的第id个元素,sum表示剩余的可瓜分的q,外面放个used数组记录以及选择过的元素。
至于赛场上,怎么想出来这种题,让我严格推导是推不出的,也许,真的得靠直觉吧。
#include<bits/stdc++.h>
#define ll long long
#define a(x) ((x<<1)-1)
using namespace std;
int n,cnt;
bool used[2010]; // 第i个元素被使用
inline bool dfs(int id, int sum){
if (sum == 0){
cnt = id-1;
return true;
}
for (int nxt=n;nxt>=1;--nxt){
if (sum < a(nxt)) continue;
if (!used[nxt]){
used[nxt] = true;
if (dfs(id+1,sum-a(nxt))) return true;
used[nxt] = false;
}
}
return false;
}
inline void solve(){
scanf("%d",&n);
if (n<=6 || n%2==0){
printf("NO");
return;
}
printf("YES\n");
for (int i=1;i<=n;++i) printf("%d ",a(i));
putchar('\n');
for (int i=1;i<=n;++i){
memset(used,0,sizeof(used));
used[i] = true;
dfs(1,(n*n-a(i))/2);
printf("%d ",cnt);
for (int j=1;j<=n;++j){
if (i!=j && used[j]) printf("%d ",j);
}
putchar('\n');
}
}
int main(){
solve();
return 0;
}
I - Rabbit
签到题,unique去重,特判一下0和1。
移除1没有影响。
有一个0时答案为1,有大于一个0时答案为0。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,a[100010];
inline void solve(){
scanf("%d",&n);
bool one = false;
int zero = 0;
for (int i=1;i<=n;++i){
scanf("%d",&a[i]);
if (a[i] == 1) one = true;
if (a[i] == 0) ++zero;
}
sort(a+1,a+1+n);
n = unique(a+1,a+1+n) - (a+1);
if (zero==1) printf("1\n");
else if (zero>1) printf("0\n");
else printf("%d\n",n-one);
}
int main(){
solve();
return 0;
}
J - Cube
模拟的签到题。
题目下面列出了所有情况,有些规律:第一行必然有且仅有一个格,并必和第三行第一个格对应;第二行第一列的格必和第三列的格对应;剩下的枚举寻找就好了。
分别求出a、b的状态(一样的判断方式,复制粘贴改一下变量就好了),看看两者状态是否一样。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int T,a[3][4],b[3][4],mp1[10],mp2[10];
inline void solve(){
for (int i=0;i<3;++i) for (int j=0;j<4;++j) scanf("%d",&a[i][j]);
for (int i=0;i<3;++i) for (int j=0;j<4;++j) scanf("%d",&b[i][j]);
memset(mp1,0,sizeof(mp1));
int up = 0;
for (int j=0;j<4;++j) if (a[0][j] != 0) up = a[0][j];
for (int j=0;j<4;++j) if (a[2][j] != 0) {
mp1[up] = a[2][j];
mp1[a[2][j]] = up;
break;
}
mp1[a[1][0]] = a[1][2];
mp1[a[1][2]] = a[1][0];
mp1[a[1][1]] = 7;
for (int i=0;i<3;++i) for (int j=0;j<4;++j){
if (a[i][j] != 0 && mp1[a[i][j]] == 0){
mp1[a[i][j]] = a[1][1];
mp1[a[1][1]] = a[i][j];
break;
}
}
memset(mp2,0,sizeof(mp2));
up = 0;
for (int j=0;j<4;++j) if (b[0][j] != 0) up = b[0][j];
for (int j=0;j<4;++j) if (b[2][j] != 0) {
mp2[up] = b[2][j];
mp2[b[2][j]] = up;
break;
}
mp2[b[1][0]] = b[1][2];
mp2[b[1][2]] = b[1][0];
mp2[b[1][1]] = 7;
for (int i=0;i<3;++i) for (int j=0;j<4;++j){
if (b[i][j] != 0 && mp2[b[i][j]] == 0){
mp2[b[i][j]] = b[1][1];
mp2[b[1][1]] = b[i][j];
break;
}
}
for (int i=1;i<=6;++i){
if (mp1[i]!=mp2[i]){
printf("NO");
return;
}
}
printf("YES");
}
int main(){
solve();
return 0;
}
K - Surround the Buildings
全场无人AC的毒瘤题,但质量也确实高。
本人周日打重现,只想出了先生成villages的凸包,再O(m^2)枚举forts两两之间的连线,每次O(logn)判断直线是否穿过凸包,将连边加入图,然后转化为图论问题,求图的最小环。但因为代码难度太高而没写。
周一开始补题,看官方题解,才发现要把不穿过凸包的直线按绕凸包的一个方向加入图(这里选取逆时针方向),求有向图的最小环,否则有些环会不包含凸包。
图论部分没法用并查集和拓扑排序法,网上的求最小环是针对洛谷P2661信息传递的特殊情况,那道题点出度最多为1。我照着题解写了BFS+bitset的写法,也是没见过,很厉害。
然后还有一些要注意的点,要去掉重复的点,本题不保证点不重复。
代码挺难写的,周一开始写,周二才第一次提交,周三才通过。
前前后后提交了20次才AC,这种题应该是设计来合作完成的,一个人写计几部分,一个人写图论部分,要一个人写出来真的很难。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef bitset<2010> BS;
struct Point{
ll x,y;
Point operator - (const Point p)const{return Point{x-p.x,y-p.y};}
ll operator * (const Point p)const{return x*p.y-y*p.x;}
bool up()const{ // 判断象限
if (y == 0){
if (x < 0) return true;
else return false;
}else return y > 0;
}
bool operator < (const Point p)const{ // 叉积排序精度更高
if (up()){
if (!p.up()) return true;
}else if (p.up()) return false;
return (*this)*p > 0;
}
bool operator == (const Point p)const{return x==p.x&&y==p.y;}
}v[1000010],f[2010],s[1000010],ss[1000010]; // v[]为villages,f[]为forts,s[]为凸包上点,ss[]为凸包上边对应的向量
int n,m,tbcnt;
queue<int> q;
BS E[2010],uvs;
int dis[2010];
inline int binarySearch(Point p){
int res = upper_bound(ss+1,ss+1+tbcnt,p) - ss;
if (res == tbcnt+1) return 1;
return res;
}
inline void addEdge(int a, int b){ // 加有向边,经过凸包的有向边舍弃
int p1 = binarySearch(f[a]-f[b]); // 求平行于有向边两向量与凸包切点
int p2 = binarySearch(f[b]-f[a]);
ll prd1 = (f[b]-f[a])*(s[p1]-f[a]);
ll prd2 = (f[b]-f[a])*(s[p2]-f[a]);
if (prd1 >= 0 && prd2 >= 0) E[a][b] = 1; // 方向相同且两点都在有向边逆时针侧
else if (prd1 <= 0 && prd2 <= 0) E[b][a] = 1; // 方向相同且两点都在有向边顺时针侧
// 否则方向相异,有向边经过凸包,舍弃该有向边
}
inline void Graham(){ // 求village凸包上的点,存到数组s[]里
tbcnt = 2;
s[1] = v[1], s[2] = v[2];
for (int i=3;i<=n;++i){
while ((s[tbcnt]-s[tbcnt-1])*(v[i]-s[tbcnt]) < 0) --tbcnt;
s[++tbcnt] = v[i];
}
}
inline void bfs(){
int ans = 1e8;
for (int i=1;i<=m;++i){
while (!q.empty()) q.pop();
for (int j=1;j<=m;++j){
if (E[i][j]) dis[j] = 1, uvs[j] = 0, q.push(j);
else dis[j] = -1, uvs[j] = 1;
}
while (!q.empty() && dis[i] == -1){
int now = q.front();
q.pop();
BS B = E[now]&uvs;
while (B.any()){
int u = B._Find_first();
B[u] = 0;
uvs[u] = 0;
q.push(u);
dis[u] = dis[now] + 1;
}
}
if (dis[i] != -1) ans = min(ans, dis[i]);
}
printf("%d\n",ans==1e8?-1:ans);
}
inline ll sqr(ll x){return x*x;}
inline ll sqrdis(Point a, Point b){return sqr(a.x-b.x)+sqr(a.y-b.y);}
inline void solve(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i) scanf("%lld%lld",&v[i].x,&v[i].y);
for (int i=2;i<=n;++i){
if (v[i].y < v[1].y) swap(v[i],v[1]);
else if (v[i].y == v[1].y && v[i].x < v[1].x) swap(v[i],v[1]);
}
sort(v+2,v+1+n,[](Point a,Point b){
ll cro = (a-v[1])*(b-v[1]);
if (cro > 0) return true;
if (cro == 0 && sqrdis(a,v[1])<sqrdis(b,v[1])) return true;
return false;
});
n = unique(v+1,v+n+1) - (v+1); // 去重
Graham();
s[n+1] = s[1];
for (int i=1;i<=n;++i) ss[i] = s[i+1]-s[i];
for (int i=1;i<=m;++i) scanf("%lld%lld",&f[i].x,&f[i].y);
sort(f+1,f+1+m,[](Point a,Point b){return a.x==b.x?a.y<b.y:a.x<b.x;}); // 去重前先排序
m = unique(f+1,f+1+m) - (f+1);
for (int i=1;i<=m;++i) for (int j=1;j<i;++j) addEdge(i,j);
bfs();
}
int main(){
solve();
return 0;
}