2021年ICPC第九届陕西省赛题解(10/12)

结合官方榜与自己做题体验,按难度做题顺序: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

B - Cell

C - GCD

D - Disease

E - Swapping Game

F - Code

G - Interesting Set

I - Rabbit

J - Cube

K - Surround the Buildings


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,答案为 \prod_{i=1}^{T}((\frac{tot-ovf}{tot})^{q})_{i}

逆元不解释。

#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步,则答案 ans = \sum_{x=0}^{k}ansx[x]*ansy[k-x]*\textrm{C}_{k}^{x}

其中ansx[x]表示横向走x步、终点位于[0,n]范围内方案数。

设向左走了a步,向右走了x-a步,

要求终点>=0 a>=x-a\Rightarrow a>=\left \lceil \frac{x}{2} \right \rceil

要求终点<=n a-(x-a)<=n\Rightarrow a<=\left \lfloor \frac{n+x}{2} \right \rfloor

ansx[x] = \sum_{a=\left \lceil \frac{x}{2} \right \rceil}^{min(x,\left \lfloor \frac{n+x}{2} \right \rfloor)} \textrm{C}_{x}^{a}

由公式\textrm{C}_{a}^{b-1}+\textrm{C}_{a}^{b}=\textrm{C}_{a+1}^{b}(杨辉三角),可以将ansx[x]乘二、处理一下首末项得到ansx[x+1],于是ansx[x]可以O(1)推到ansx[x+1],于是可以预处理出ansx[x]。

同理 ansy[k-x] = \sum_{b=\left \lceil \frac{k-x}{2} \right \rceil}^{min(k-x,\left \lfloor \frac{m+k-x}{2} \right \rfloor)} \textrm{C}_{k-x}^{b},也可以预处理。

所以最终答案 ans = \sum_{x=0}^{k}(\sum_{a=\left \lceil \frac{x}{2} \right \rceil}^{min(x,\left \lfloor \frac{n+x}{2} \right \rfloor)} \textrm{C}_{x}^{a}*\sum_{b=\left \lceil \frac{k-x}{2} \right \rceil}^{min(k-x,\left \lfloor \frac{m+k-x}{2} \right \rfloor)} \textrm{C}_{k-x}^{b}*\textrm{C}_{k}^{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

题意可转化为求 \sum_{i=1}^{r}(\left \lfloor \frac{r}{i} \right \rfloor - \left \lfloor \frac {l-1}{i} \right \rfloor \geqslant k),其中括号内的表达式为真时值为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点得病且传染的概率给父节点为 \frac{a}{b}g[i], 则

g(i)=f[i]+(1-f[i])(1-\prod_{i \hspace{1mm} son \hspace{1mm} j}(1-\frac{a}{b}g[j]))(其中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层不"得病且传染") = \prod_{stp[i]=t}(1-\frac{a}{b}g[i])(这里a/b是指i传染给亲节点的概率)

P(t层完全不得病) = \prod_{stp[i]=t}(1-g[i])

P(t层之上初始时都不得病) = \prod_{stp[i]<t}(1-f[i])

所以答案 ans=g[1]+\sum_{t=2}^{maxstp}(t*(\prod_{stp[i]=t}(1-\frac{a}{b}g[i])-\prod_{stp[i]=t}(1-g[i]))*\prod_{stp[i]<t}(1-f[i]))

此外还有乘法逆元和负数取模(代码中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删去一个元素后分割为两个相等的集合,必须满足 (s-a_{i})%2==0

n是偶数时,若S中全是偶数,将所有ai不断除以2可以得到等价的非全偶数解;若S中全是奇数,s则为偶数,删去任何一个ai,s为减为奇数,不满足 (s-a_{i})%2==0;只剩下S中又有奇数又有偶数的情况,然而由 (s-a_{i})%2==0知任何一个ai奇偶性与s相同。三种情况都不行,则n不可能为偶数。

对于n>=7且n为奇数时,总能找到解,解的形式与样例相似,即前n个奇数构成的集合。

这种情况下,首先s肯定为奇数,满足 (s-a_{i})%2==0,实际上我们可以算出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;
}

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值