2018-2019 ICPC Northwestern European Regional Programming Contest (NWERC 2018)【Training 2】

2018-2019 ICPC Northwestern European Regional Programming Contest (NWERC 2018)

题目ABCDEFGHIJK
solved🚫🚫🚫---🚫🚫

✔:比赛时通过;🚫:赛后通过;⚪:比赛时尝试但是未通过

总结:

  • 反向思维。B题是一个简单的拓扑排序,如果能想到反向建图,那就是一道裸题。当然正向建图也只要加一个dfs,只能怪自己太菜。
  • 读题速度。K题时做出来人数最多的,但是我们花了很久很久很久很久很久才看懂。英语水平有待提高。

A. Access Points【数学】

solved by Sstee1XD.(-)

题意: 给你n个网点的坐标,你要放n个横纵坐标都不为降序的点,与对应的网点连线,求最短距离平方和。

题解: 题目的意思可以写成这样的数学公式。
∑ i = 1 n ( X i − x i ) 2 + ∑ i = 1 n ( Y i − y i ) 2 \sum_{i=1}^n {(X_i - x_i)^2} + \sum_{i=1}^n {(Y_i - y_i)^2} i=1n(Xixi)2+i=1n(Yiyi)2
  所以就分解成了横纵坐标的两个子问题,我们接下来以横坐标为例。
  如果网点的各个横坐标也是非降序的,那么每个点就安排到网点上,距离和就为 0 0 0。如果有两个点,后面的点的坐标比前面的小,这时发现两个点重合才能让距离和最小。我们土想一下,觉得取两个值的中间值能让距离和最小。在数理上,我们设重合点的坐标为d,网点的坐标为 X 1 , X 2 X_1,X_2 X1X2,那么他们的距离平方和为 ( X 1 − d ) 2 {(X_1 - d)}^2 (X1d)2 + ( X 2 − d ) 2 {(X_2 - d)}^2 (X2d)2。因为 X 1 , X 2 X_1,X_2 X1X2已知,我们可以看成一个一元二次方程,易得当 d = ( X 1 + X 2 ) 2 d = \frac{(X_1 + X_2)}{2} d=2(X1+X2)时取得最小值。推广到一般式,设总共有n个数字,得 d = ∑ i = 1 n ( X i ) n d=\frac{\sum_{i=1}^n{(X_i)}}{n} d=ni=1n(Xi),证明猜想正确。
  接下来我们只用判断后面的网点的坐标是否比前一个坐标小就行了。如果小,就进行合并操作。同时注意到一次可能会向前合并多个点,所以维护一个单调栈。判断坐标大小时,用 坐 标 和 合 并 的 点 的 数 量 \frac{坐标和}{合并的点的数量} 进行比较,稍微做下乘法减小误差即可。

#include <bits/stdc++.h>
 
using namespace std;
 
const int maxn = 1e5 + 7;
 
#define fi first
#define se second
typedef long long ll;
 
template<class T> inline int read(T &x) {
    int f = 0;x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (f) x = -x;
    return x;
}
 
int n, x[maxn], y[maxn];
double ans;
 
void gao(int a[]) {
    stack<pair<ll, int>> st;
    st.push({a[1], 1});
    for (int i = 2; i <= n; ++i) {
        ll s = a[i];
        int num = 1;
        while (!st.empty() && st.top().fi * num >= st.top().se * s) {
            s += st.top().fi;
            num += st.top().se;
            st.pop();
        }
        st.push({s, num});
    }
    for (int i = n; i >= 1; --i) {
        double ave = double(st.top().fi) / st.top().se;
        int num = st.top().se;
        st.pop();
        while (num--) {
            ans += (ave - a[i]) * (ave - a[i]);
            i--;
        }
        i++;
    }
}
 
void solve(){
    for (int i = 1; i <= n; ++i) read(x[i]), read(y[i]);
    gao(x);
    gao(y);
    printf("%.8f\n", ans);
}
 
int main() {
    read(n);
    solve();
    return 0;
}

B. Brexit Negotiations【拓扑排序+dfs/反图】

solved by lllllan.(-)

题意: 需要开n个会议,一些特定会议开始之前需要先完成别的特定会议,并且在开始之前需要话k分钟总结先前完成的k场会议,每场会议都有原本需要的时长Ti。求单场耗时最长的会议的最小值。

题解: 某些会议必须在完成某些会议之后才能开始,显然是拓扑排序。如果正向建图,肯定会将可以开始的会议中挑选耗时最长的先开始,但是这样的话,可能会碰到一些极端情况无法处理:
  第二和第三个会议需要在第一个会议之后,而一到三个会议的耗时分别为1,2, 1。第四个会议需要在第三个会议之后,时长为100。如果按照简单的贪心思想,结束第一个会议之后,会从二三中间选择时间较长的二号会议先开始,而原本耗时最长的四号会议则会被置为最后。

  • DFS + 拓扑排序: 从入度为零的会议开始dfs,记录每条递归路线之后,出现过的耗时最长的会议。在进行拓扑排序时,若遇到可以同时在多个会议中选择一个先开始,则以这个dfs结果为依据,将存在较大耗时的路线先开始。

  • 反向建图 + 拓扑排序: 如果一开始就建反图,那么几乎就是一道裸题了:

DFS + 拓扑排序

#include<bits/stdc++.h>
#define bug cout << "bug********************************\n"
 
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
 
inline int read(){
   int s = 0, w = 1;
   char ch = getchar();
   while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
   while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
   return s * w;
}
 
const int N = 4e5 + 10;
int tim[N], deg[N], tag[N];
vector<int> V[N];
struct node{
	int id, time, tag;
	node(int a, int b, int c){id = a; time = b; tag = c;}
	bool operator<(const node &A)const{
		if(A.tag == tag)	return A.time > time;
		return A.tag > tag;}
};
priority_queue<node>Q;

int dfs(int u){
	if(tag[u])	return tag[u];
	int ta = tim[u];
	for(int i = 0; i < V[u].size(); i++){
		int v = V[u][i];
		tag[v] = dfs(V[u][i]);
		ta = max(ta, tag[v]);
	}
	return ta;
}

void run(int n){
	int maxt = 0;
	int R = 0;
	for(int i = 1; i <= n; i++)
		if(deg[i] == 0)
			tag[i] = dfs(i);
	for(int i = 1; i <= n; i++)
		if(deg[i] == 0)
			Q.push(node(i, tim[i], tag[i]));
	while(!Q.empty()){
		node s = Q.top(); Q.pop();
		int u = s.id;
		int t = s.time + R;
		R++;
		maxt = max(maxt, t);
		for(int i = 0; i < V[u].size(); i++){
			int v = V[u][i];
			deg[v]--;
			if(deg[v] == 0)	Q.push(node(v, tim[v], tag[v]));
		}
	}
	printf("%d\n", maxt);
}
 
int main(){
	int n = read();
	for(int i = 1; i <= n; i++){
		tim[i] = read();
		int d = read();
		for(int j = 0; j < d; j++){
			int u = read();
			V[u].push_back(i);
			deg[i]++;
		}
	}
	run(n);
	return 0;
}

反向建图 + 拓扑排序

#include<bits/stdc++.h>
#define bug cout << "bug********************************\n"
 
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
 
inline int read(){
   int s = 0, w = 1;
   char ch = getchar();
   while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
   while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
   return s * w;
}
 
const int N = 4e5 + 10;
int tim[N], deg[N];
vector<int> V[N];
struct node{
	int id, time;
	node(int a, int b){id = a; time = b; }
	bool operator<(const node &A)const{
		return A.time < time;}
};
priority_queue<node>Q;
void run(int n){
	int maxt = 0;
	int R = n - 1;
	for(int i = 1; i <= n; i++){
		if(deg[i] == 0)	Q.push(node(i, tim[i]));
	}
	while(!Q.empty()){
		node s = Q.top(); Q.pop();
		int u = s.id;
		int t = s.time + R;
		maxt = max(maxt, t);
		R--;
		for(int i = 0; i < V[u].size(); i++){
			int v = V[u][i];
			deg[v]--;
			if(deg[v] == 0)	Q.push(node(v, tim[v]));
		}
	}
	printf("%d\n", maxt);
}
 
int main(){
	int n = read();
	for(int i = 1; i <= n; i++){
		tim[i] = read();
		int d = read();
		for(int j = 0; j < d; j++){
			int u = read();
			V[i].push_back(u);
			deg[u]++;
		}
	}
	run(n);
	return 0;
}

C. Circuit Board Design【数学+dfs】

solved by Sstee1XD.(-)

题意: 给你树上点之间的相邻关系,让你安排每个节点的二维坐标,要求每条边边长为 1 1 1,各点各边之间有一定距离。

题解: 因为只有 1000 1000 1000个点,计算一下把半圆分成 1000 1000 1000份也是可以的,直接写就行了。注意要半圆,不然极端情况下线会有交叉。

#include <bits/stdc++.h>
 
using namespace std;
 
#define pie acos(-1.0) / 1010
 
const int maxn = 1007;
 
int n;
double x[maxn], y[maxn];
vector<int> G[maxn];
int f;
 
int dfs(int u, int fa) {
	for (auto v : G[u]) {
		if (v == fa) continue;
		x[v] = x[u] + cos(f * pie), y[v] = y[u] + sin(f * pie);
		f++;
		dfs(v, u);
	}
	return f + 1;
}
int main(){
	scanf("%d", &n);
	for (int i = 1, u, v; i < n; ++i) {
		scanf("%d %d", &u, &v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1, 0);
	for (int i = 1; i <= n; ++i) {
		printf("%.8f %.8f\n", x[i], y[i]);
	}
}

G. Game Design【玄学构造】

solved by Sstee1XD.(-)

题意: 小球在二维坐标系上滚动,遇到障碍物后停止,之后可以转换方向。 ( 0 , 0 ) (0,0) (0,0)点是一个洞,小球滚进去后就不能再移动了。现在给你小球的运动轨迹,请输出一种可能的开始点的坐标,障碍物的数量以及各个障碍物的坐标。

题解: 因为小球滚进洞后就不能移动了,所以最后的答案如果有形如 RLR 之类的三次反复横跳就是不合法的。我们假设小球从 ( 0 , 0 ) (0,0) (0,0)点出发,最后整个运动轨迹进行平移就可以得到答案。我们再假设小球滚动的单位长度为 d i s dis dis,如果小球在反复横跳,就不扩大 d i s dis dis的距离,否则进行扩大,最后卡出答案。

#include <bits/stdc++.h>
 
using namespace std;
 
map<char, int> re;
set<pair<int, int>> st;
int num[2];
char s[30];
 
void Init() {
	re['U'] = 'D', re['D'] = 'U', re['R'] = 'L', re['L'] = 'R';
}
 
void solve() {
	int len = strlen(s);
	if (len >= 3 && re[s[len - 1]] == s[len - 2] && re[s[len - 2]] == s[len - 3]) {
		puts("impossible");
		return;
	}
	int x = 0, y = 0, dis = 5;
	for (int i = 0; i < len; ++i) {
		if (s[i] == 'U') y = dis, st.insert({x, y + 1});
		if (s[i] == 'D') y = -dis, st.insert({x, y - 1});
		if (s[i] == 'R') x = dis, st.insert({x + 1, y});
		if (s[i] == 'L') x = -dis, st.insert({x - 1, y});
		if (s[i + 1] != re[s[i]]) {
			dis += 5;
		}
	}
	printf("%d %d\n", -x, -y);
	printf("%d\n", int(st.size()));
	for (auto v : st) {
		printf("%d %d\n", v.first - x, v.second - y);
	}
}
 
int main() {
	Init();
	scanf("%s", s);
	solve();
}

H. Hard Drive【构造】

solved by Sstee1XD.00:40(+2)

题意: 构造一个01序列,其中有b个位置一定为0,其中最后一位一定为0,第一位永远不会一定为0,最终要使这个序列01变换c次。

题解: 若第一位为1,只能提供1次贡献。别的地方只要10这样填就能一个1贡献两次。所以先判断一下c是否为奇数,决定第一位是否为1,然后让c成为偶数填就行了。

#include<bits/stdc++.h>
#define bug cout << "bug********************************\n"
 
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
 
inline int read(){
   int s = 0, w = 1;
   char ch = getchar();
   while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
   while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
   return s * w;
}
const int maxn = 5e5 + 7;
int n, c, b;
int a[maxn], bro[maxn];
 
void run(){
	int v;
	for (int i = 1; i <= b; ++i) {
		v = read();
		bro[v] = 1;
	}
	int head = 2;
	if (c & 1) a[1] = 1, c--, head = 3;
	if (!c) return;
	for (int i = head; i <= n; ++i) {
		int len = 0, l = i;
		while (!bro[i] && i <= n) {
			len++;
			i++;
		}
		for (int j = l; j < l + len; j += 2) {
			a[j] = 1;
			c-= 2;
			if (!c) return;
		}
	}
}
 
int main(){
	scanf("%d %d %d", &n, &c, &b);
	run();
	for (int i = 1; i <= n; ++i) printf("%d", a[i]);
	return 0;
}

I. Inflation【思维】

solved by Tryna. 0:55(+1)

题意:给出n个容量为1-n的气球和n个含有气体的体积,问在使气球不爆炸的情况下,使每个气球都能填充到最大的体积比。

#include<bits/stdc++.h>
#define bug cout << "bug********************************\n"
 
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
 
inline int read(){
   int s = 0, w = 1;
   char ch = getchar();
   while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
   while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
   return s * w;
}
const int maxn = 200010;
int n;
double a[maxn],b[maxn],maxx;
 
int main(){
	scanf("%d",&n);
	int flag = 0;
	for(int i = 1;i <= n;i++){
		scanf("%lf",&a[i]);
		if(a[i] == 0) flag = 1;
		b[i] = i;
	}
	sort(a+1,a+1+n);
	maxx = inf;
	for(int i = 1;i <= n;i++){
		double f = a[i]/b[i];
		if(f>1){
			flag = 2;
			break;
		}
		else{
			if(f < maxx) maxx = f;
		}
	}
	if(flag == 2) printf("-1\n");
	else if (flag == 1) printf("0\n");
	else printf("%.7f\n",maxx);
	return 0;
}

J. Jinxed Betting【思维】

solved by Sstee1XD.(-)

题意: 给你 n n n个数,第一个数为Julia的积分,其他的为其他人的积分,保证Julia的积分是最大的(并不严格最大)。他们进行betting游戏,赢的人获得一积分。Julia每次选择和除她以外的拥有最高积分的人相同的选项,如果存在多个人,就选择最多人选择的选项,问Julia的积分至少能保持几轮最大。

题解: 我们构造一下发现每次选积分最多的人中的一半的人赢,能让积分增长得最快。设积分最多的人总共有 n n n个,那么能在 l o g 2 ( n ) + 1 log_2(n) + 1 log2(n)+1次操作后让这群人的积分都增长 l o g 2 n log_2{n} log2n。同时要注意到这时可以让所有剩下的积分不是最高的人都增长积分,并且若积分排名第二的人群与积分第一的人群积分差只有 1 1 1的话,进行完这轮积分增加后就能合并进积分第一的人群,重复操作直至积分超过Julia,即可得到答案。注意要进行一些计算的操作来减少时间复杂度。

#include <bits/stdc++.h>

using namespace std;

#define endl "\n"
#define dbg(x...)             \
    do {                      \
        cout << #x << " -> "; \
        err(x);               \
    } while (0)

void err() {
    cout << endl;
}

template <class T, class... Ts>
void err(const T& arg, const Ts&... args) {
    cout << arg << ' ';
    err(args...);
}

typedef long long ll;

const int maxn = 1e5 + 7;
int n;
ll a[maxn];

struct Node{
	ll num;
	ll d;
}node[maxn];

void solve() {
	for (int i = 1; i <= n; ++i) cin >> a[i];
	sort(2 + a, a + n + 1, greater<ll>());
	if (a[1] == a[2]) {
		cout << a[1] - a[3] << endl;
		return;
	}
	int f = 1;
	for (int i = 2; i <= n; ++i) {
		ll cnt = 1;
		while (i < n && a[i] == a[i + 1]) {
			i++;
			cnt++;
		}
		node[f++] = {a[i], cnt};
	}
	ll maxx = a[2], tot = node[1].d, ans = 0, step = 0;
	int nxt = 2;
	while (maxx + log2(tot) <= a[1]) {
		ll go = log2(tot) + 1;
		ll add = log2(tot);
		step += go;
		if (nxt >= f) {
			ll mul = (a[1] - maxx) / add;
			ans += mul * go;
			maxx += mul * add;
			break;
		}	
		ll t = maxx + add - step - node[nxt].num;
		if (t > 0) {
			if (maxx + (t + 1) * add > a[1]) {
				ll mul = (a[1] - maxx) / add;
				ans += mul * go;
				maxx += mul * add;
				break;
			}
			else {
				add += t * add;
				step += go * t;
				go += t * go;
			}
		}
		maxx += add;
		tot += node[nxt++].d;
		ans += go;
	}
	ans += a[1] - maxx;
	cout << ans << endl;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);cout.tie(nullptr);
	cin >> n;
	solve();
}

K. Kleptography【签到】

solved by Tryna. 2:29(+)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 300;
int n,m;
char k[maxn],a[maxn],b[maxn];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    cin>>(k + m - n);
    cin>>b;
    for(int i = m - 1;i >= n; i--){
        a[i] = b[i] - k[i] + 'a';
        if(a[i] < 'a') a[i] += 26;
        k[i - n] = a[i];
    }
    cout<<k<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值