2019-2020 ICPC Northwestern European Regional Programming Contest (NWERC 2019) 部分题解

I - Inverted Deck
题意:让你选一个区间进行翻转,问能不能是数组递增
思路:我们枚举每个数 a i a_i ai,如果存在一个最大的 j ∈ [ i + 1 , n ] j\in[i+1,n] j[i+1,n] a j < a i a_jaj<ai,那么必然是要翻转这个区间 [ i , j ] [i,j] [i,j]的,把这个区间转过来check一下即可。

#include 
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
int n,a[N];
int l,r;
set<pair<int,int>>s;
int chec(){
	for(int i=2;i<=n;i++){
		if(a[i]<a[i-1])return 0;
	}
	return 1;
}
int main() {
  ios::sync_with_stdio(false);
  cin>>n;l=n+1,r=0;
  for(int i=1;i<=n;i++)cin>>a[i],s.insert({a[i],-i});
	for(int i=1;i<=n;i++){
		auto x=*s.begin();
		if(x.fi<a[i]){
			int l=i,r=-x.se;
			reverse(a+l,a+1+r);
			if(chec()){
				return cout<<l<<' '<<r,0;
			}else{
				return cout<<"impossible\n",0;
			}
		}
		s.erase({a[i],-i});
	}
	cout<<"1 1\n";
	//1  3 2 
 	return 0;
}

F - Firetrucks Are Red
大意:n个人,每个人都有喜欢的数字,如果两个人喜欢相同的一个数字,那么称这两个人有直接联系,如果两个不直接联系的人与同一个人有直接联系,则称这两个人为间接联系。问n个人互相能不能成为直接或间接联系。
思路:建图跑一个生成树即可。

#include 
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
int n,c[N],cnt;
struct uzi{
	int s,t,d;
}p[N];
int vis[N];
vector<int>v[N],g[N];
vector<uzi>ans;
int f[N];
int find(int x){
	return f[x]==x?x:(f[x]=find(f[x]));
}
int main() {
  ios::sync_with_stdio(false);
  cin>>n;
  for(int i=1;i<=n;i++){
  	f[i]=i;
  	int m;
  	cin>>m;
  	for(int j=1;j<=m;j++){
  		int s;
  		cin>>s;
  		c[++cnt]=s;
  		v[i].pb(s);
  	}
  }
  sort(c+1,c+1+cnt);
  int len=unique(c+1,c+1+cnt)-c-1;
  for(int i=1;i<=n;i++){
  	for(auto &k:v[i]){
  		k=lower_bound(c+1,c+1+len,k)-c;
  		g[k].pb(i);
  	}
  }
  int cnt=0;
  for(int i=1;i<=len;i++){
  	for(int j=1;j<g[i].size();j++){
  		p[++cnt]={g[i][j],g[i][j-1],c[i]};
  	}
  }	
  for(int i=1;i<=cnt;i++){
  	int x=find(p[i].s),y=find(p[i].t);
  	if(x==y)continue;
  	f[x]=y;
  	ans.pb({p[i]});
  }
  if(ans.size()==n-1){
  	for(auto k:ans){
  		cout<<k.s<<' '<<k.t<<' '<<k.d<<'\n';
  	}
  }else{
  	cout<<"impossible\n";
  }
 	return 0;
}

H. Height Profile
大意:有一个山,给你每公里位置上的高度。让你求坡度 ≥ k \geq k k的最长连续水平长度,定义坡度 k k k 垂 直 高 度 水 平 长 度 \frac{垂直高度}{水平长度} .
思路: h j − h i j − i ≥ k \frac{h_j-h_i}{j-i}\geq k jihjhik转化这个式子, h j − k j ≥ h i − k i h_j-kj\geq h_i-ki hjkjhiki
那么定义数组 b i = h i − k i b_i=h_i-ki bi=hiki,排序找到每个位置能向左延伸的最长长度。
由于左右还能延伸一定的距离,所有分情况讨论一下(延伸必然延伸到斜率大的一边)。二分找出延伸的距离即可。
ps:一开始排序的地方用线段树实现的…

#include 
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
int n,k,a[N],b[N],f[N],can[N];
const int inf=1e9;
void gao(){
	for(int i=0;i<=n;i++){
		f[i]=i;
		can[i]=inf;
	}
	sort(f,f+1+n,[](int x,int y){
		if(b[x]==b[y])return x<y;
		return b[x]<b[y];
	});
	int l=f[0];
	for(int i=1;i<=n;i++){
		if(f[i]<l){
			l=min(l,f[i]);
			continue;
		}
		can[f[i]]=l;
		l=min(l,f[i]);
	}
}
int main() {
	double ch;
  scanf("%d%d",&n,&k);
  for(int i=0;i<=n;i++)scanf("%d",a+i);  
 	for(int i=1;i<=k;i++){
 		double l;
 		scanf("%lf",&l);
 		int L=l*10+0.5;
 		long double ans=-1;int sta=0;
 		for(int j=0;j<=n;j++){
 			b[j]=a[j]-L*j;
 			if(j&& a[j]-a[0]>=L*j )ans=j,sta=1;
 		}
 		gao();
 		for(int j=1;j<=n;j++){
 			int ne=can[j];
 			if(ne==inf)continue;
 			sta=1;
 			if(!ne){
 				if(j==n)ans=max(ans,(long double)(j-ne));
 				else{
	 				long double l=j,r=j+1,can=j;
	 				long double k=a[j+1]-a[j];
	 				for(int xa=1;xa<=30;xa++){
	 					long double _mid=(l+r)/2;
	 					long double n=a[j]+k*(_mid-j);
	 					if((n-a[ne])>=L*(_mid-ne))can=_mid,l=_mid;
	 					else r=_mid;
	 				}
	 				ans=max(ans,can-ne);					
 				}
 			}else{
 				long double k1=a[ne]-a[ne-1];
 				long double k2=a[j+1]-a[j];
 				if(ne){
 					long double l=ne-1,r=ne,can=ne;
 					long double k=a[ne]-a[ne-1];
	 				for(int xa=1;xa<=30;xa++){
	 					long double _mid=(l+r)/2;
	 					long double n=a[ne-1]+k*(_mid-ne+1);
	 					if((a[j]-n)>=L*(j-_mid))can=_mid,r=_mid;
	 					else l=_mid;
	 				}
	 				ans=max(ans,j-can); 					
 				}
 				if(j+1<=n){
	 				long double l=j,r=j+1,can=j;
	 				long double k=a[j+1]-a[j];
	 				for(int xa=1;xa<=30;xa++){
	 					long double _mid=(l+r)/2;
	 					long double n=a[j]+k*(_mid-j);
	 					if((n-a[ne])>=L*(_mid-ne))can=_mid,l=_mid;
	 					else r=_mid;
	 				}
	 				ans=max(ans,can-ne);		 					
 				}
 			}
 		}
 		if(!sta)puts("-1");
 		else printf("%.10Lf\n",ans);
 	}
 	
 	return 0;
}

A. Average Rank
大意:n个人 初始每个人都是0分,m场比赛,每次比赛有k个人得一分。
定义排名为,按分数由大到小排序,排名递增,同分同排名。
思路:显然每个人的分数是逐渐递增的(每次+1/0),那么我们可以很轻松的知道多于某个分数 x x x的人数 c n t x cnt_x cntx,设 s u b x sub_x subx为第 i i i次操作前,分数 x x x的排名之和。设 s u b x sub_x subx上一次更新的位置为 l a s t x last_x lastx,那么在 l a s t x − > i − 1 last_x->i-1 lastx>i1的操作中,分数 x x x的排名都是不变的,那么显然可以很轻松的计算出 s u b x sub_x subx变大了多少。
每个人在每场加分比赛后,分数都会变化,那么在他上一次变化到前一次中的分数 x x x都没变,那么我们可以通过记录这个人第一次变成 x x x分之前的 s u b x sub_x subx来获得他在两次更新中间真正在分数 x x x下获得的排名之和(注意此时未把最新一次操作的排名算进去)。
最后的时候,把所有分数在m次比赛后的 s u b sub sub算出来,再更新一下每个人的 s u m sum sum,因为之前计算的排名都是从0开始的,所以每个人的排名之和要加m。

#include 
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
int n,w;
int cnt[N],last[N],p[N];
LL sum[N],sub[N],pre[N];
int a[N];
int main() {
  ios::sync_with_stdio(false);
  cin>>n>>w;
  for(int i=1;i<=w;i++){
  	int k;
  	cin>>k;
  	for(int j=1;j<=k;j++){
  		int x;
  		cin>>x;
  		//他的分数是 p[x] 
  		sub[p[x]]+=1ll*(i-last[p[x]])*cnt[p[x]];
  		//更新这个分数的排名 的和
  		++cnt[p[x]];//人数++
  		last[p[x]]=i;//最后一次更新 p[x]的 分 是第i个操作
  		sum[x]+=sub[p[x]]-pre[x];//更新 到这个人上去
  		++p[x];//+分
  		sub[p[x]]+=1ll*(i-last[p[x]])*cnt[p[x]];//更新这个分数的和
  		last[p[x]]=i;
  		pre[x]=sub[p[x]];
 
  	}
	}
	for(int i=1;i<=n;i++){
		sub[p[i]]+=1ll*(w+1-last[p[i]])*cnt[p[i]];
		last[p[i]]=w+1;
		sum[i]+=sub[p[i]]-pre[i]+w;
	}
  cout<<fixed<<setprecision(8);
  for(int i=1;i<=n;i++){
  	cout<<1.0*sum[i]/w<<'\n';
  }
 	return 0;
}

你可能感兴趣的:(gym)