Grakn Forces 题解

目前进度:A~F


后悔没打了(那天晚上我在看电影)……赛后 E F 直接秒……然后就只能 orz GM wjz 了/se

  • tzc:这场前六题也就 B 有点质量了。
  • 我:这不是 ntf 原话吗?

Portal

A - Circle Coloring

就随便做了吧。。。

#include<bits/stdc++.h>
using namespace std;
void mian(){
	int n;
	cin>>n;
	vector<int> a(n+1),b(n+1),c(n+1),ans(n+1);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);
	for(int i=1;i<=n;i++)scanf("%d",&c[i]);
	for(int i=1;i<=n;i++){
		if(i==1)ans[i]=a[i];
		else if(i<n)ans[i]=a[i]==ans[i-1]?b[i]:a[i];
		else ans[i]=a[i]==ans[i-1]||a[i]==ans[1]?b[i]==ans[i-1]||b[i]==ans[1]?c[i]:b[i]:a[i];
		printf("%d ",ans[i]);
	}
	puts("");
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

B - Arrays Sum

注意到我们最终要把 (a_i) 减去所有的 (b_{j,i}) 得到 (0),它的一个必要条件是减去所有 (b_{j,i}) 后所有 (a_i) 要相等。

而一个 (b) 的一个不相等相邻对最多能挽救 (a) 的一个不相等相邻对。又因为一个 (b) 中最多有 (k) 个不相同数,所以一个 (b) 最多能挽救 (k-1) 个不相邻对。

  • (k-1=0) 时,此时无法挽救相邻对,必须要一开始全部相等才能有解。然后又分全为 (0) 和全非 (0),答案分别是 (0)(1),有手就行;
  • (k-1>0) 时,意味着可以挽救,设有 (cnt) 个不相等相邻对,于是答案是 (leftlceildfrac{cnt}{k-1} ight ceil)。此时不需要考虑将非 (0) 变成 (0),因为可以一边挽救一边变。
#include<bits/stdc++.h>
using namespace std;
void mian(){
	int n,k;
	cin>>n>>k;
	vector<int> a(n+1);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	a[0]=a[1];
	int cnt=0;
	for(int i=1;i<=n;i++)cnt+=a[i-1]!=a[i];
	if(cnt==0)cout<<(a[0]==0?0:1)<<"
";
	else{
		if(k==1)puts("-1");
		else cout<<(cnt+k-2)/(k-1)<<"
";
	}
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

C - Discrete Acceleration

我们计算出对于每个 flag,两个人走到他分别需要多长时间,这个前后缀加一下即可算出。

然后就可以找到他们相遇的那个段。解个方程即可得到相遇时间。

#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int n;
double m;
double a[N+2];
double tim1[N+2],tim2[N+2];
void mian(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)scanf("%lf",a+i);
	a[n+1]=m;
	tim1[0]=tim2[n+1]=0;
	for(int i=1;i<=n+1;i++)tim1[i]=tim1[i-1]+(a[i]-a[i-1])/i;
	for(int i=n;~i;i--)tim2[i]=tim2[i+1]+(a[i+1]-a[i])/(n-i+1);
	for(int i=1;i<=n+1;i++)if(tim1[i]>=tim2[i]){
		double x=(tim2[i]-tim1[i-1]+(a[i]-a[i-1])/(n-i+2))/(1./i+1./(n-i+2));
//		printf("%.10lf %.10lf %.10lf!
",x,tim1[i-1]+x/i,tim2[i]+((a[i]-a[i-1])-x)/(n-i+2));
		return printf("%.10lf
",tim1[i-1]+x/i),void();
	}
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

D - Searchlights

这个 D 还算有点东西。

考虑枚举竖着走的距离,然后对于每个距离,将它加上每个人最小的横着走的距离的最大值更新答案。

先考虑将灯的覆盖范围给搞成按横坐标排序后纵坐标递减的形式。这样显然每个人的「最小的横着走的距离」有 (mathrm O(m)) 次变化的断点。如果我们对于每个距离都能直接精准无误的找到所有被改变的人并修改,用个 set 维护即可轻松 (mathrm O(|V|+nmlog))。问题在于如何精准无误的找。

其实很简单。只需要预处理对于每个人的所有断点,压进对应竖着走的距离所对应的 vector 里即可。

然后 set 那个部分努努力是可以做到线性的,这里(和代码)就不写了。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
#define ppb pop_back
const int inf=0x3f3f3f3f;
const int N=2000,M=N,V=1000000;
int n,m;
pair<int,int> a[N+1];
pair<int,int> b[M+1];
vector<pair<int,int> > v;
vector<pair<int,int> > chg[V+1];
int now[N+1];
int cnt[V+1];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i].X>>a[i].Y;
	for(int i=1;i<=m;i++)cin>>b[i].X>>b[i].Y;
	sort(b+1,b+m+1);
	v.pb(mp(-1,V+1));
	for(int i=1;i<=m;i++){
		while(v.back().Y<=b[i].Y)v.ppb();
		v.pb(b[i]);
	}
	v.pb(mp(V+1,-1));
//	for(int i=0;i<v.size();i++)cout<<v[i].X<<" "<<v[i].Y<<"!
";
	for(int i=1;i<=n;i++)for(int j=0;j+1<v.size();j++)
		chg[max(0,v[j].X-a[i].X+1)].pb(mp(i,max(0,v[j+1].Y-a[i].Y+1)));
	int ans=inf;
	multiset<int> st;
	for(int i=1;i<=n;i++)st.insert(0);
	for(int i=0;i<=V;i++){
		for(int j=0;j<chg[i].size();j++){
			int old=now[chg[i][j].X],nw=chg[i][j].Y;
			now[chg[i][j].X]=chg[i][j].Y;
			st.erase(st.find(old)),st.insert(nw);
		}
		ans=min(ans,*--st.end()+i);
	}
	cout<<ans;
	return 0;
}

E - Avoid Rainbow Cycles

首先考虑建图。直接按题意建图是平方的,我们考虑将集合当作虚拟节点建图,这样边数是线性的。

这样就建成了一张二分图,左部是集合,右部是元素。

然后一个 rainbow 就是从左边到右边,从右边到左边,如此反复,最终回到自己,还要求不能经过重复节点,即是个简单环。然后你就发现,题目要求没有 rainbow,没有简单环不就是没有环嘛……

于是要删除代价最小,就是要留下来的代价最大,跑个 Kruskal 最大生成树就可以了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int M=100000,N=M;
int m,n;
int a[M+1],b[N+1];
vector<pair<int,pair<int,int> > > eg;
struct ufset{
	int fa[N+M+1];
	void init(){memset(fa,0,sizeof(fa));}
	int root(int x){return fa[x]?fa[x]=root(fa[x]):x;}
	void mrg(int x,int y){fa[root(x)]=root(y);}
}ufs;
signed main(){
	cin>>m>>n;
	for(int i=1;i<=m;i++)scanf("%lld",a+i);
	for(int i=1;i<=n;i++)scanf("%lld",b+i);
	int tot=0;
	for(int i=1;i<=m;i++){
		int s;
		scanf("%lld",&s);
		while(s--){
			int x;
			scanf("%lld",&x);
			eg.pb(mp(a[i]+b[x],mp(i,m+x)));
			tot+=a[i]+b[x];
		}
	}
	sort(eg.begin(),eg.end());
	reverse(eg.begin(),eg.end());
	ufs.init();
	int ans=0;
	for(int i=0;i<eg.size();i++){
		int x=eg[i].Y.X,y=eg[i].Y.Y,len=eg[i].X;
		if(ufs.root(x)!=ufs.root(y))ans+=len,ufs.mrg(x,y);
	}
	cout<<tot-ans;
	return 0;
}

F - Two Different

就这?也配放到 F 的位置上?

考虑分治。我们假设一个长度为偶数的序列左半边全部相等,右半边也全部相等,那么显然可以左右一一配对,来实现整个序列都相等。于是我们可以分治 (log) 层,每层进行 (dfrac12 n) 个操作,总次数 (dfrac 12nlog n)

但是不保证 (n)(2) 的整次幂,所以可以学习 ST 表的查询,分成极大的 (2) 的整次幂的前缀和后缀,各来一次,总次数 (nlog n)

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
int n,m;
vector<pair<int,int> > ans;
void sol(int base){
	for(int i=1;i<=m/2;i*=2){
		for(int j=1;j<=m;j++)if((j+i-1)/i&1)ans.pb(mp(j+base,j+i+base));
	}
}
int main(){
	cin>>n;
	m=1<<int(log2(n));
	sol(0),sol(n-m); 
	cout<<ans.size()<<"
";
	for(int i=0;i<ans.size();i++)printf("%d %d
",ans[i].X,ans[i].Y);
	return 0;
}
原文地址:https://www.cnblogs.com/ycx-akioi/p/grakn-forces-solution.html