2020 ICPC Universidad Nacional de Colombia Programming Contest【Training 4】

2020 ICPC Universidad Nacional de Colombia Programming Contest

题目ABCDEFGHIJKLMN
solved🚫----🚫--

✔:比赛时通过;🚫:赛后通过;⚪:比赛时尝试了未通过;-:比赛时未尝试


A. Approach

solved by Tryna. (-)

题意 :给出四个点,前两个点确定一条直线,后面两个点确定另外一条直线。有两个人,分别从两条直线的起点以同样的速度出发,当一个人到达终点时,另外一个人继续前进,问两个人移动过程中的最短距离。

题解 :这两个人前进过程中的距离函数是一个有极值的凹函数,所以只需要三分一下时间,求出该时间下两点的坐标,然后在求出距离。需要注意一个人到达终点后,另外一个人继续前进,所以需要分成两段,第一段为0 - min(d1,d2),第二段为min(d1,d2) - max(d1,d2) 。还有个难点就是求出该时间下各个点的坐标。需要预先用反正切求出直线与x轴的夹角,然后配合正弦函数和余弦函数求出点的坐标,最后在计算两点距离。注意这题卡精度,要限定三分的次数。

#include<bits/stdc++.h>
using namespace std;    
const double pi = acos(-1);
#define inf 0x3f3f3f3f
#define ll long long
const int maxn = 20010;
const int mod = 1e9 + 7;
double X1,X2,X3,X4,Y1,Y2,Y3,Y4,angle1,angle2,d1,d2,midl,midr;
double getDist(double ax, double ay, double bx, double by){
    return sqrt((ax - bx)*(ax - bx) + (ay - by)*(ay - by));
}
double f(double t){
    double t1 = min(d1, t);
    double t2 = min(d2, t);
    double xt1 = t1*cos(angle1);
    double yt1 = t1*sin(angle1);
    double xt2 = t2*cos(angle2);
    double yt2 = t2*sin(angle2);
    double _x1 = X1 + xt1;
    double _y1 = Y1 + yt1;
    double _x2 = X3 + xt2;
    double _y2 = Y3 + yt2;
    return getDist(_x1, _y1, _x2, _y2);
}
double sf(double l, double r){
    for(int i = 1;i <= 10000; i++){
        midl = (l + r)/2;
        midr = (midl + r)/2;
        if(f(midl) > f(midr)) l = midl;
        else r = midr;
    }
    return f(midr);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>X1>>Y1>>X2>>Y2;
    cin>>X3>>Y3>>X4>>Y4;

    angle1 = atan2(Y2 - Y1, X2 - X1);
    angle2 = atan2(Y4 - Y3, X4 - X3);
    
    d1 = getDist(X1,Y1,X2,Y2);
    d2 = getDist(X3,Y3,X4,Y4);
    
    double mn = min(d1,d2);
    double mx = max(d1,d2);
    double result = min(sf(0, mn), sf(mn, mx));
    cout<<fixed<<setprecision(12)<<result<<endl;
    return 0;
}

B. Baby name

solved by Sstee1XD. 03:03(+3)

题意 :给你父亲和母亲的名字,各取父亲和母亲名字的一个字串,将它们拼在一起成为子代的名字,问最大字典序的名字。

题解:分别把父母亲名字中最大的字母的下标记录下来,最后再比较各个下标开始的字串字典序大小。这里注意一下连续相同的字母只用取第一位,比较连续的字母是没有意义的。得到两个字符串的下标之后,先输出父亲的第一个字母,再一个个比较,最后输出母亲的字串。

P.S:刚开始没想清楚直接冲了一发,后来想出来写代码没写完整,但是以为会RE,报了WA,考虑了很久才交最后一发。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
 
#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...);
}
const int maxn = 2e5 + 7;
 
char s1[maxn], s2[maxn];
 
int m1 = 0, m2 = 0;
int n1 = 0, n2 = 0;
 
int G1[maxn >> 1], G2[maxn >> 1];
 
int gao(int G[], int n, int len, char s[]) {
	int ans = G[0];
	for (int i = 1; i <= n; ++i) {
		int tmp = G[i];
		int a = ans;
		while (tmp < len && s[a] == s[tmp]) {
			a++;
			tmp++;
		}
		if (tmp < len && s[a] < s[tmp]) ans = G[i];
	}
	return ans;
}
 
void run(){
	int len1 = strlen(s1), len2 = strlen(s2);
	G1[0] = 0, G2[0] = 0;
	for (int i = 1; i < len1; ++i) {
		if (s1[i] > s1[m1]) {
			m1 = i;
			n1 = 0;
			G1[n1] = i;
			while (i < len1 && s1[i + 1] == s1[i]) ++i;
		}
		else if(s1[i] == s1[m1]) {
			G1[++n1] = i;
			while (i < len1 && s1[i + 1] == s1[i]) ++i;
		}
	}
	for (int i = 1; i < len2; ++i) {
		if (s2[i] > s2[m2]) {
			m2 = i;
			n2 = 0;
			G2[n2] = i;
			while (i < len2 && s2[i + 1] == s2[i]) ++i;
		}
		else if(s2[i] == s2[m2]) {
			G2[++n2] = i;
			while (i < len2 && s2[i + 1] == s2[i]) ++i;
		}
	}
	m1 = gao(G1, n1, len1, s1);
	m2 = gao(G2, n2, len2, s2);
	printf("%c", s1[m1]);
	m1++;
	while (m1 < len1 && s1[m1] >= s2[m2]) {
		printf("%c", s1[m1++]);
	}
	for (int i = m2; i < len2; ++i) printf("%c", s2[i]);
}
 
int main(){
	scanf("%s %s", s1, s2);
	run();
	return 0;
}

E. Enter to the best problem of this contest!

solved by Sstee1XD. 01:37(+1)

题意 : 一个人藏在一个最多 29 29 29层的二叉树里,你最多可以询问29次,每次询问一个节点,它会返回人藏在的节点和你询问的节点的距离,最后输出人所在的节点。

题解:这题运用到二叉树的性质。我们第一次询问点 1 1 1,即可得到点所在的深度。然后每次询问最左边的节点,设得到的距离为 d d d,当前节点的层数减去 d 2 \frac{d}{2} 2d即可得到它和目标节点的 L C A LCA LCA,就能确定目标节点在另一边的子树上。重复这个操作就能得到目标节点的位置。在极端情况下,每次询问只能让子树的层数减少 1 1 1,由于第一次询问并不能减少子树的层数,所以为了保证 29 29 29次能找完,最后询问到 d < = 2 d <= 2 d<=2即停止。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define endl "\n"
 
#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...);
}
 
int qpow(int a, int b) {
	int res = 1;
	for(; b; b >>= 1) {
		if (b & 1) res *= a;
		a *= a;
	}
	return res;
}
 
int n;
 
void gao(int d) {
	int now = qpow(2, d);
	d = 10;
	while (d > 2) {
		cout << endl << now << endl;
		cout.flush();
		cin >> d;
		if (d == 0) break;
		now /= qpow(2, d / 2);
		now = now * 2 + 1;
		now *= qpow(2, d / 2 - 1);
	}
	cout << endl << "! " << now;
	cout.flush(); 
}
 
void run(){
	int d;
	cout << 1 << endl;
	cout.flush();
	cin >> d;
	if (d == 0) {
		cout << endl << "! 1" << endl;
		return;
	}
	gao(d);
}
 
int main(){
//	freopen("in.txt", "r", stdin);
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	run();
	return 0;
}

G. Great dinner

solved by Tryna .1:04(+1)

题解 :n!除 m^2

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
 
#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...);
}
const ll mod = 1e9 + 7;
const int maxn = 100010;
int n , m;
ll ans = 1;
int main() {
	cin >> n >> m;
	for(int i = 1, u, v; i <= m; i++) cin >> u >> v;
	for(int i = 2; i <= n ; i++) {
		ans = ans * i;
		if(ans % 2 == 0 && m) {
			m--;
			ans /= 2;
		}
		if(ans >= mod)	ans %= mod;
	}
	cout << ans << endl;
	return 0;
}

I. Incredible photography

solved by Sstee1XD.(-)

题意 :给你一段序列,以某点为起点,你的移动规则是找到一个你能看到的高度严格大于你的点,然后你可以移动到那个点,之后又可以以相同的规则进行移动。求出以每个点为起点时能移动的最长距离。

题解 :每个点都要找,很像一道记忆化搜索,又看到了视野中严格大于,就想到了单调栈,所以就用单调栈去维护左右要去的点就行了。当视野中有相同高度的点时,我们要想移动的距离最长,就要选择能看到的最远的点,同时也要注意到,相同高度的点可能是不连续的,比如5 3 5 3 5这个例子。最后debug的时候也是过了这个例子A的,但是感觉写得有点麻烦。

#include <bits/stdc++.h>
using namespace std;

#define endl "\n"

typedef long long ll;

const int maxn = 1e5 + 7;
ll l[maxn], r[maxn], n, h[maxn], ans[maxn];
ll sta[maxn], top;

vector<ll> G[maxn];

ll dfs(int u) {
    if (ans[u] != -1) return ans[u];
    ll tl = 0, tr = 0;
    if (l[u] != 0) tl = max(u - l[u] + dfs(l[u]), tl);
    if (r[u] != 0) tr = max(r[u] - u + dfs(r[u]), tr);
    ans[u] = max(tl, tr);
    return ans[u];
}


void solve() {
    memset(ans, -1, sizeof(ans));
    for (int i = 1; i <= n; ++i) cin >> h[i];
    for (int i = 1; i <= n; ++i) {
        int left = i;
        int tag = 0;
        while (h[i] == h[i + 1] && i < n) i++;
        while (top && h[i] >= h[sta[top - 1]]) {
            if (h[sta[top - 1]] == h[i]) {
                tag = sta[top - 1];
                break;
            }
            r[sta[top - 1]] = i;
            G[i].push_back(sta[top - 1]);
            top--;
        }
        for (int j = left; j <= i; ++j) {
            sta[top++] = j;
        }
        if (tag) {
            for (int j = 0; j < G[tag].size(); ++j) {
                G[i].push_back(G[tag][j]);
                r[G[tag][j]] = i;
            }
        }
    }
    top = 0;
    for (int i = 1; i <= n; ++i) G[i].clear();
    for (int i = n; i >= 1; --i) {
        int tag = 0;
        int right = i;
        while (h[i] == h[i - 1] && i > 1) i--;
        while (top && h[i] >= h[sta[top - 1]]) {
            if (h[sta[top - 1]] == h[i]) {
                tag = sta[top - 1];
                break;
            }
            l[sta[top - 1]] = i;
            G[i].push_back(sta[top - 1]);
            top--;
        }
        for (int j = right; j >= i; --j) {
            sta[top++] = j;
        }
        if (tag) {
            for (int j = 0; j < G[tag].size(); ++j) {
                G[i].push_back(G[tag][j]);
                l[G[tag][j]] = i;
            }
        }
    }
    for (int i = 1; i <= n; ++i) dfs(i);
    for (int i = 1; i <= n; ++i) {
        if (i > 1) cout << ' ';
        cout << ans[i];
    }
    cout << endl;
 }

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

K. Katastrophic sort

solved by Tryna.0:29(+)

题意 :给定两个只含字母和数字的字符串,保证字母部分长度相等,字母和字母比较,若相同,则比较数字。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
 
#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...);
}
const int maxn = 100010;
char a1[maxn],b2[maxn],a2[maxn],b1[maxn];
string st1,st2;
int main(){
	cin>>st1;
	cin>>st2;
	int cnt = 0;
	for(int i = 0;i<st1.size();i++){
		if(st1[i]<='z'&&st1[i]>='a'){
			cnt++;
			a1[cnt] = st1[i];
		}
		else break;
	}
	for(int i = cnt;i<st1.size();i++) a2[i-cnt+1] = st1[i];
	cnt = 0;
	for(int i = 0;i<st2.size();i++){
		if(st2[i]<='z'&&st2[i]>='a'){
			cnt++;
			b1[cnt] = st2[i];
		}
		else break;
	}
	for(int i = cnt;i<st2.size();i++) b2[i-cnt+1] = st2[i];
	if(strcmp(a1+1,b1+1)>0){
		cout<<">"<<endl;
	}
	else if(strcmp(a1+1,b1+1)<0) cout<<"<"<<endl;
	else{
		if(strlen(a2+1)<strlen(b2+1)) cout<<"<"<<endl;
		else if(strlen(a2+1)>strlen(b2+1)) cout<<">"<<endl;
		else{
			if(strcmp(a2+1,b2+1)>0) cout<<">"<<endl;
			else if(strcmp(a2+1,b2+1)<0) cout<<"<"<<endl;
			else cout<<"="<<endl;
		}
	}
	return 0;
}

L. Lonely day

solved by lllllan、Sstee1XD.04:52(+13)

题意: 给定一个n*m的图,途中的’S’为起点,‘E’为终点。每次移动只能横向或纵向地移向最近的一个干净的点’.'或终点,'X’为脏的点。如果能到达终点,则输出步数最少并且字典序最小的路径(下移为D、左移为L、右移为R、上移为U)。否则输出-1。

曲折经历: 比赛时想的没有那么周到,用BFS+DFS,结构体里携带各种参数,并且实时传递移动路径,导致空间占用比较大,加上本来算法的效率就不高,所以WA的和TLE的次数累计到达了13发。最后各种修改,卡常1980ms极限AC。又因为参加过这个比赛和做过这个题目的人很少,我甚至找不到题解可以学习。所以自己又花了一个上午的时间研究,最后得出一个时间和空间以及代码长度都最优的代码(对我的能力而言)

最终思路:

  • BFS。 众所周知:一个图 + 起点到终点 + 最短路径 = BFS。定义一个结构体的队列,结构体node中只需携带某个点的坐标即可。定义vis[][] 避免重复访问。
  • pre_x[][]、pre_y[][]。 因为广搜的特性,从某个点出发,最多可能有四个可达的目标点。而因为vis避免重复访问,每个点一定只能来自于一个点。所以定义两个数组来记录某一个干净的点的前驱节点的坐标。
  • DFS。 用BFS一直搜索到终点,结束广搜。我们记录路径的方式是用两个pre数组记录前驱节点的坐标,并没有记录移动的方向。所以可以通过dfs从终点递推回起点,比较每个点与前驱节点的坐标关系,记录下移动方向。最后倒叙输出答案即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10;
int n, m;
int sx, sy, cnt;	//sx\sy记录起点坐标。cnt为路径的字符个数
int pre_x[N][N];	//记录前驱节点的坐标
int pre_y[N][N];	
int vis[N][N];		//访问标记,避免重复访问
char G[N][N];		//存图
char ans[2 * N];	//记录路径
int dir[][2] = {{1, 0}, {0, -1}, {0, 1}, {-1, 0}};
char Dir[] = "DLRU";
struct node {	int x, y;};
 
int check(int x, int y) {	//检查移动后是否还在图的范围内
	if(x < 1 || x > n || y < 1 || y > m)	return 0;
	return 1;
}

void dfs(int x, int y) {	//从终点推回起点,标出移动路径
	if(x == sx && y == sy){	//回到起点,输出答案
		cout << cnt << endl;
		for(int i = cnt - 1; i >= 0; i--)	cout << ans[i];
		return;
	}
	int prex = pre_x[x][y];
	int prey = pre_y[x][y];
	if(prey == y)		//比较节点与前驱节点的关系,得出移动方向
		if(prex < x)	ans[cnt++] = Dir[0];
		else			ans[cnt++] = Dir[3];
	else
		if(prey < y)	ans[cnt++] = Dir[2];
		else			ans[cnt++] = Dir[1];
	dfs(prex, prey);
}
 
void BFS() {
	queue<node> Q;
	Q.push({sx, sy});
	vis[sx][sy] = 1;
	while(!Q.empty()) {
		node u = Q.front(); Q.pop();
		if(G[u.x][u.y] == 'E') {	//找到终点,dfs找到移动路径
			dfs(u.x, u.y);	return ;
		}
		for(int i = 0; i < 4; i++) {
			int nex = u.x + dir[i][0];
			int ney = u.y + dir[i][1];
			while(check(nex, ney)) {	
				if(vis[nex][ney])	break;
				if(G[nex][ney] == '.' || G[nex][ney] == 'E'){
					vis[nex][ney] = 1;
					pre_x[nex][ney] = u.x;
					pre_y[nex][ney] = u.y;
					Q.push({nex, ney});
					break;	//找到最近的第一个干净点,插入队列即可推出
				}
				//干净点不一定相邻,可能隔着几个'X'才能到达,所以需要不断移动查找
				nex += dir[i][0], ney += dir[i][1];
			}
		}
	}
	cout << -1 << endl;
}
 
int main() {
	ios :: sync_with_stdio(false);	cin.tie(0);	cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; i++) 
		for(int j = 1; j <= m; j++) {
			cin >> G[i][j];
			if(G[i][j] == 'S')
				sx = i, sy = j;
		}
	BFS();
	return 0;
}

M. Magic spells

solved by Sstee1XD. 00:52(+2)

题意:给你一个主串,然后给你n个子串,询问是否存在公共子序列符合子串,输出能符合的最长的子串。

题解:用一个 d p dp dp二维数组来记录这位之后各个字母第一次出现的位置,从后往前预处理一下,就可以开始做了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
 
#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...);
}
 
const int maxn = 2e5 + 10;
char s[maxn];
int dp[maxn][30];
int id[maxn];
int ch(char c) {
	return c - 'a' + 1;
}
int n;
char str[maxn], ans[maxn];
void run(){
	memset(id, -1, sizeof(id));
	memset(dp, -1, sizeof(dp));
	int len = strlen(s);
	id[ch(s[len - 1])] = len - 1;
	for (int i = len - 2; i >= 0; --i) {
		for (int j = 1; j <= 26; ++j) dp[i][j] = id[j];
		id[ch(s[i])] = i;
	}
	scanf("%d", &n);
	while (n--) {
		scanf("%s", str);
		int f = id[ch(str[0])];
		int num = 1;
		if (f == -1) {
			puts("IMPOSSIBLE");
			continue;
		}
		int rear = strlen(str);
		f = dp[f][ch(str[num])];
		while (f != -1 && num < rear) {
			num++;
			f = dp[f][ch(str[num])];
		}
		str[num] = '\0';
		printf("%s", str);
		puts("");
	}
}
 
int main(){
	scanf("%s", s);
	run();
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值