USACO 2019 December Contest, Silver 题解

1.MooBuzz

Description

Farmer John 的奶牛们最近成为了一个简单的数字游戏“FizzBuzz”的狂热玩家。这个游戏的规则很简单:奶牛们站成一圈,依次从一开始报数,每头奶牛在轮到她的时候报一个数。如果一头奶牛将要报的数字是 3 的倍数,她应当报“Fizz”来代替这个数。如果一头奶牛将要报的数字是 5 的倍数,她应当报“Buzz”来代替这个数。如果一头奶牛将要报的数字是 15 的倍数,她应当报“FizzBuzz”来代替这个数。于是这个游戏的开始部分的记录为:
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16

由于词汇的匮乏,奶牛们玩的 FizzBuzz 中用“Moo”代替了 Fizz、Buzz、FizzBuzz。于是奶牛版的游戏的开始部分的记录为:

1, 2, Moo, 4, Moo, Moo, 7, 8, Moo, Moo, 11, Moo, 13, 14, Moo, 16

给定 N(1≤N≤1e9),请求出这个游戏中第 N 个被报的数。

A possible solution

利用容斥原理+二分判断。我们假设 n 个数里共有 cnt 个被跳过的数,那么报的第 n 个数就是 n+cnt ,由于 n 和 cnt 都具有单调性,所以我们可以利用二分判定来解决。
其中容斥原理很容易想到,cnt 显然等于 n 内 3 的倍数 + 5 的倍数 +15的倍数,但是15 = 3 * 5,所以15不需要重复统计(已经在 3 的倍数中统计过了)。但是我们重复统计了 3 和 5 的公倍数(统计了 2 次),于是我们就要减去同时是 3 的倍数和 5 的倍数 的 个数即可得到cnt。
在O(1)时间内求出cnt后,我们就可以在O(logN)时间内用二分搜索解决此题。

Code
#include
using namespace std;
typedef long long ll;
const ll INF = 1e18;
int tn;
int check(ll n){
	ll a = n/3, b = n/5;
	ll c = n/15;
	ll cnt = a + b - c;
	return n-cnt >= tn;
}
int main(){
	freopen("moobuzz.in","r",stdin);
	freopen("moobuzz.out","w",stdout);
	cin >> tn ;
	ll l = 1,r = INF;
	while(l <= r){
		ll mid = l+r>>1;
		if(check(mid)) r = mid-1;
		else l = mid+1;
	}
	cout << l << endl;
	return 0;
}

2.Meetings

Description

有两个牛棚位于一维数轴上的点 0 和 L 处(1≤L≤1e9)。同时有 N 头奶牛(1≤N≤5e4)位于数轴上不同的位置(将牛棚和奶牛看作点)。每头奶牛 i 初始时位于某个位置 xi,并朝着正向或负向以一个单位每秒的速度移动,用一个等于 1 或 −1 的整数 di 表示。每头奶牛还拥有一个在范围 [1,1e3] 内的重量。所有奶牛始终以恒定的速度移动,直到以下事件之一发生:
如果奶牛 i 移动到了一个牛棚,则奶牛 i 停止移动。
当奶牛 i 和 j 占据了相同的点的时候,并且这一点不是一个牛棚,则发生了相遇。此时,奶牛 i 被赋予奶牛 j 先前的速度,反之亦然。注意奶牛可能在一个非整数点相遇。
令 T 等于停止移动的奶牛(由于到达两个牛棚之一)的重量之和至少等于所有奶牛的重量之和的一半的最早时刻。请求出在时刻 0…T(包括时刻T)之间发生的奶牛对相遇的总数。

A possible solution

这道题与 Ants 那题类似。如果不考虑重量,那么我们可以忽略每头牛的个性,从整体来看,如果两头牛相遇可以看作他们“穿过”对方并继续前进;如果每头牛有不同的重量,那么它们就有了个性,不能再等价于“穿过”对方。
这个时候我们可以从两个角度来考虑:

  1. 首先如果有 x 头牛回窝,那么一定是靠近端点的 x 头牛。原因很简单,这是一个一维的空间,前面的牛不回窝后面的就会被堵住过不去。所以每个端点回窝的重量顺序就确定了。
  2. 在 t 时刻内,一头牛只可能和与他迎面走来的 2 * t 距离内的牛相遇。这个时候我们不需要考虑重量,所以每个牛可以看作“穿过”相遇的对方继续前进,那么显然 t 时刻内这两头牛相向而行 2 * t 的距离。

通过第一个推论我们可以求出 T (二分或者优先队列),通过第二个推论我们可以根据 T 来求出相遇的牛的总数。

Code
#include
using namespace std;
const int N = 1e5+10;
const int INF = 0x3f3f3f3f;
typedef long long ll;
int n,l;
ll sum;
int w[N],px[N],d[N];
bool check(int t){
	//t时刻到家的重量是否达到一半
	int tw = 0, c1 = 0, c2 = 0;
	for(int i = 1;i <= n;i++){
		if(d[i] == 1 && l - px[i] <= t) c1++;
		if(d[i] == -1 && px[i] <= t) c2++;
	}
	for(int i = 1;i <= c2;i++) tw += w[i];
	for(int i = n;i > n-c1;i--) tw += w[i];
	//printf("%d %d %d %d %d\n",t,c1,c2,tw,sum);
	return tw*2 < sum;
}
ll a[N];
int tot = 0;
int bsearch(ll x){
	int l = 1,r = tot;
	while(l <= r){
		int mid = l+r>>1;
		if(a[mid] >= x) r = mid-1;
		else l = mid+1;
	}
	//cout << x << " " << l << endl;
	return l;
}
int calc(ll t){
	int res = 0;	//相遇次数
	tot = 0;
	for(int i = 1;i <= n;i++)
		if(d[i] == -1) a[++tot] = px[i];
	for(int i = 1;i <= n;i++)
		if(d[i] == 1) res += bsearch(px[i]+2*t+1) - bsearch(px[i]);
	return res;
}
struct Node{
	int w,x,d;
	bool operator < (const Node& rhs) const{
		return x < rhs.x;
	}
}nodes[N];
int main(){
	freopen("meetings.in","r",stdin);
	freopen("meetings.out","w",stdout);
	scanf("%d%d",&n,&l);
	for(int i = 1;i <= n;i++)
		scanf("%d%d%d",&nodes[i].w,&nodes[i].x,&nodes[i].d);
	sort(nodes+1,nodes+1+n);
	for(int i = 1;i <= n;i++) w[i] = nodes[i].w,px[i] = nodes[i].x,d[i] = nodes[i].d;
	for(int i = 1;i <= n;i++) sum += w[i];
	int tl = 0,tr = l; 
	while(tl <= tr){
		int mid = tl+tr>>1;
		if(check(mid)) tl = mid+1;
		else tr = mid-1;
	}
	//cout << l << endl;
	printf("%d\n",calc(tl));	//计算 l 秒内碰头次数
	return 0;
}

3.Milk Visits

Description

Farmer John 计划建造 N(1≤N≤1e5)个农场,用 N−1 条道路连接,构成一棵树(也就是说,所有农场之间都互相可以到达,并且没有环)。每个农场有一头奶牛,品种为更赛牛或荷斯坦牛之一。
Farmer John 的 M 个朋友(1≤M≤1e5)经常前来拜访他。在朋友 i 拜访之时,Farmer John 会与他的朋友沿着从农场 Ai 到农场 Bi 之间的唯一路径行走(可能有 Ai=Bi)。除此之外,他们还可以品尝他们经过的路径上任意一头奶牛的牛奶。由于 Farmer John 的朋友们大多数也是农场主,他们对牛奶有着极强的偏好。他的有些朋友只喝更赛牛的牛奶,其余的只喝荷斯坦牛的牛奶。任何 Farmer John 的朋友只有在他们访问时能喝到他们偏好的牛奶才会高兴。

请求出每个朋友在拜访过后是否会高兴。

A possible solution

一条道路只有 3 种情况,全是 H 牛,全是 G 牛或者两者兼有。那么如果节点 x 是 H 牛我们就令其w = -1,如果是 G 牛就等于1。那么这一段路径上所有点的 w 相加,如果等于 d,就说明全是G牛;如果等于 -d,就说明全是 H 牛;否则就说明两者都有。
至于快速求两点间距离可以用倍增求LCA。

Code
#include
using namespace std;
const int N = 1e5+10;
const int M = 2*N;
char ty[N];
int n,m,w[N];
int head[N],ver[M],edge[M],nex[M],tot = 1;
void addEdge(int x,int y,int z){
	ver[++tot] = y; edge[tot] = z;
	nex[tot] = head[x]; head[x] = tot;
}
int dis[N],anc[N][25],dep[N],sum[N];
void dfs(int x){
	for(int i = 1;i <= 22;i++) 
		anc[x][i] = anc[anc[x][i-1]][i-1];
	for(int i = head[x];i ;i = nex[i]){
		int y = ver[i], z = edge[i];
		if(dis[y] || y == 1) continue;
		dep[y] = dep[x] + 1;
		dis[y] = dis[x] + z;
		anc[y][0] = x;
		sum[y] = sum[x] + w[y];
		dfs(y);
	}
}
int lca(int x,int y){
	if(dep[x] < dep[y]) swap(x,y);
	for(int i = 22;i >= 0;i--)
		if(dep[anc[x][i]] >= dep[y]) x = anc[x][i];
	if(x == y) return x;
	for(int i = 22;i >= 0;i--){
		if(anc[x][i] != anc[y][i]){
			x = anc[x][i]; y = anc[y][i];
		}
	}
	return anc[x][0];
}

int main(){
	freopen("milkvisits.in","r",stdin);
	freopen("milkvisits.out","w",stdout);
	scanf("%d%d",&n,&m);
	scanf("%s",ty+1);
	for(int i = 1;i <= n;i++) 
		if(ty[i] == 'G') w[i] = 1;
		else w[i] = -1;
	for(int i = 1,x,y;i < n;i++){
		scanf("%d%d",&x,&y);
		addEdge(x,y,1); addEdge(y,x,1);
	}
	char s[10];
	dep[0] = -1; dfs(1);
	for(int i = 1,x,y;i <= m;i++){
		scanf("%d%d%s",&x,&y,s);
		int fa = lca(x,y);
		int d = dis[x]+dis[y] - 2*dis[fa] + 1;
		int sw = sum[x]+sum[y] - 2*sum[fa]+w[fa];
		if(s[0] == 'H' && sw != d) putchar('1');
		else if(s[0] == 'G' && sw != -d) putchar('1');
		else putchar('0');
	}
	return 0;
}

你可能感兴趣的:(二分)