2020 ICPC Universidad Nacional de Colombia Programming Contest 題解
M. Magic spells
題意:
一個模式串 \(T\) ,以及 \(n\) 個文本 \(s\)
輸出 \(T\) 包含 \(s\) 前綴的最長子序列
思路:
處理模式串,依次記錄所有字符出現的位置
在匹配文本串時,通過二分查找大於 模式串當前匹配位置后是否還有相同的字符,找到第一個出現的位置,並更新匹配位置
總復雜度為 \(O(log(len_t)*len_s)\)
K. Katastrophic sort
題意:
一個字符串由兩個部分構成 字母部分和數字部分
給出兩個字符串,比較字典序大小
其中數字部分的大小比較為真實數字的大小比較
如 "11" 表示 11 > "2" 表示 2
思路:
對字符串部分和數字部分分別處理一下即可
G. Great dinner
題意:
給定固定序列 \(n\) , 其中 \(m\) 個固定順序求出滿足該順序要求的排列數量
思路:
觀察可得規律 \(n! / 2^m\)
E. Enter to the best problem of this contest!
題意:
交互題
給出一個完全二叉樹,標號如下
給定樹的深度為 \(n\) , 你可以詢問最多不超過29次
詢問一個節點 \(x\) , 將會得到需要猜出的節點與當前詢問節點的最短距離。
輸出 \(!x\) 表示猜出的節點
思路:
從根節點開始
詢問 1 , 得到需要猜出節點的距離(深度),為 0 則結束
當前所猜節點深度小於所需要猜出節點深度時,詢問左兒子。如果距離增大則在右子樹,否則在左子樹。到達猜出節點深度時,向同層的兄弟節點移動。
所猜次數 小於等於 \(log(n)\)
B. Baby name
題意
給出兩個串,取兩個串中的子串並按順序拼接為一個串,使得該串字典序最大
思路
首先如何找到一個串的字典序最大的字串?
記錄字符串中最大字母出現的位置,最大位置連續的只記錄第一個出現的位置(第一個位置字典序最大)
依次比較每個起始位置的字典序大小,復雜度 \(0(n)\) 常數有點大
code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
char s[maxn],s1[maxn];
int ms[maxn],ms1[maxn];
int m,m1,cur,cur1;
//處理最大字典序子串,依次比較,2n復雜度
int func(int mss[],char ss[],int len,int cur){
int ans = mss[0];
for (int i = 1; i <= cur; ++i) {
int tmp = mss[i];
int a = ans;
while (tmp < len && ss[a] == ss[tmp]) {
a++;
tmp++;
}
if (tmp < len && ss[a] < ss[tmp]) ans = mss[i];
}
return ans;
}
int main(){
scanf("%s %s",s,s1);
int len = strlen(s);
int len1 = strlen(s1);
ms[0] = ms1[0] = 0;
m = cur = 0;
//找到該串最大字母的位置
for(int i=1;i<len;i++){
if(s[i] > s[m]){
m = i;
cur = 0;
ms[cur] = i;
//重復長度以首位置為起點,必然最大
while(i<len && s[i+1]==s[i]) ++i;
}else if(s[i]==s[m]){
ms[++cur] = i;
while(i<len && s[i+1]==s[i]) ++i;
}
}
m1 = cur1 = 0;
for(int i=1;i<len1;i++){
if(s1[i] > s1[m1]){
m1 = i;
cur1 = 0;
ms1[cur1] = i;
//重復長度以首位置為起點,必然最大
while(i<len1 && s1[i+1]==s1[i]) ++i;
}else if(s1[i]==s1[m1]){
ms1[++cur1] = i;
while(i<len1 && s1[i+1]==s1[i]) ++i;
}
}
int res1 = func(ms,s,len,cur);
int res2 = func(ms1,s1,len1,cur1);
printf("%c",s[res1]);
res1 += 1;
while(res1<len && s[res1] >= s1[res2])
printf("%c",s[res1++]);
for(int i=res2;i<len1;i++){
printf("%c",s1[i]);
}
//puts("");
}
L. Lonely day
題意
給定一個 \(n\times m\) 的圖,途中的$ ’S’ \(為起點,\)E$ 為終點。每次移動只能橫向或縱向地移向最近的一個干凈的點 \('.'\) 或終點,\('X'\) 為臟的點。如果能到達終點,則輸出步數最少並且字典序最小的路徑(下移為D、左移為L、右移為R、上移為U)。否則輸出-1
思路
最短路徑 \(BFS\) , 按照字典序大小放到隊列中, 這樣第一個到達終點的即為字典序最小且步數最小的路徑
記錄前驅,從終點進行dfs遞推回起點
code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define _rep(i,a,b) for(int i=a;i>=b;--i)
const int maxn = 2e3+10;
int n,m;
int sx,sy;
int vis[maxn][maxn];
int pre_x[maxn][maxn];
int pre_y[maxn][maxn];
int dir[][2] = {{1, 0}, {0, -1}, {0, 1}, {-1, 0}};
char dirr[] = "DLRU";
char ans[maxn<<1];
int cnt =0;
char G[maxn][maxn];
struct node
{
int x,y;
};
bool check(int x ,int y){
if(x < 1 || x > n || y < 1 || y > m ) return false;
return true;
}
void dfs(int x,int y){
if(x == sx && y== sy){
cout<<cnt<<endl;
_rep(i,cnt-1,0){
cout<<ans[i];
}
return ;
}
int prex = pre_x[x][y];
int prey = pre_y[x][y];
if(prey == y){
if(prex < x) ans[cnt++] = dirr[0];
else ans[cnt++] = dirr[3];
}else{
if(prey < y) ans[cnt++] = dirr[2];
else ans[cnt++] = dirr[1];
}
dfs(prex,prey);
}
void bfs(){
queue<node> q;
q.push(node{sx,sy});
vis[sx][sy] = 1;
while(!q.empty()){
node u = q.front();
q.pop();
if(G[u.x][u.y] == 'E') {
dfs(u.x,u.y); //回溯路徑
return ;
}
_for(i,0,3){
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(node{nex,ney});
break;
}
//沒找到 . 或者 e 之前一直移動直到移出邊界
nex += dir[i][0], ney += dir[i][1];
}
}
}
cout<<-1<<endl;
}
int main(){
IOS
cin>>n>>m;
_for(i,1,n){
_for(j,1,m){
cin>>G[i][j];
if(G[i][j] == 'S') sx = i,sy = j;
}
}
bfs();
return 0;
}
A. Approach
題意
給定二維坐標軸上給出四個點\(A,B,C,D\) , 其中 \(AB\) 、 \(CD\) 分別組成兩組線段。現有兩個點從線段\(AB\) , \(CD\) 的起點,\(A\) 和 \(C\) 以相同的速度同時出發到另一個端點。到達終點的點不再移動,直到兩個點均到達終點即結束。
求出兩點間的最短距離
思路
兩點間的距離是一個凹函數,所以采取典型的三分方法來進行縮小范圍。同時由於精度的問題,要注意三分次數。
由於兩個點有可能其中一個點停止時另一個點仍在移動,所以可以分為兩段時間來計算。
- 沒有點到達終點
- 第一個到達終點 - 所有到達終點
然后對於點所在下標,先使用tan2處理與 \(x\) 軸的夾角 \(\delta\) , 然后再使用 \(cos,sin\) 函數轉換 \(x,y\) 方向的移動距離
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll> pll;
template <typename T>
inline void rd(T& x)
{
ll tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') { if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const ll inf = 0x3f3f3f3f;
const ll mod = 1e9+7;
const ll N = 2e3 + 10;
const ll M=1e7+10;
const double eps=1e-8;
struct Point{
double x,y;
Point(){}
Point(double x, double y):x(x),y(y){}
Point operator+(const Point &B){ return Point(x+B.x,y+B.y);}
Point operator-(const Point &B){ return Point(x-B.x,y-B.y);}
Point operator*(double k){ return Point(x*k,y*k);}
void input(){
cin>>x>>y;
}
}st1,ed1,st2,ed2;
struct Line{
Point st,ed;
double len,ang;
void input(){
st.input();ed.input();
ang=atan2(ed.y-st.y,ed.x-st.x);
}
}a,b;
double Distance(Point A,Point B){
double ans=(A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
return sqrt(ans);
}
double get_dis(double mid){
Point p1=Point(a.st.x+min(mid,a.len)*cos(a.ang),a.st.y+min(mid,a.len)*sin(a.ang));
Point p2=Point(b.st.x+min(mid,b.len)*cos(b.ang),b.st.y+min(mid,b.len)*sin(b.ang));
double ans=Distance(p1,p2);
return ans;
}
double solve(double l,double r){
for(int i=1;i<=1e2;++i){
double midl=(l*2+r)/3,midr=(l+r*2)/3;
if(get_dis(l)<get_dis(r)){
r=midr;
}
else{
l=midl;
}
}
double ans=get_dis(l);
if(ans-get_dis(r)>eps){
ans=get_dis(r);
}
return ans;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
a.input();b.input();
double l=0,r=max(a.len=Distance(a.st,a.ed),b.len=Distance(b.st,b.ed));
printf("%.12lf\n",min(solve(0,min(a.len,b.len)),solve(min(a.len,b.len),max(a.len,b.len))));
return 0;
}