2020 ICPC Universidad Nacional de Colombia Programming Contest

题目传送门
队伍代码集合

A. Approach(计算几何:三分)

记录:赛后补题
思路:三分

  • 模拟过程,分两段,先是三分从A点走到B点(要使得前面的路径更短)。然后再站定在A点,三分另一个人继续走。
  • 其实第一段可以直接判断(1)是否交叉=0(2)只考虑两个起点的连线与两个终点的连线。但由于这样也符合为一个抛物线。
  • 为减少讨论,用三分处理。注意精度问题。
#include<bits/stdc++.h>
using namespace std;
double ax,ay,bx,by,cx,cy,dx,dy,len1,len2;
const double eps = 1e-12;     // 精度
double get_dis(double x1,double y1,double x2,double y2){return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);}

double check(double dis){
    // 计算长边上的点距离起点走了dis,两点的距离
    double x1,y1,x2,y2;  // 两点的坐标
    if(dis>=len1-eps) x1=bx,y1=by;
    else x1 = ax+(bx-ax)/len1*dis,y1 = ay+(by-ay)/len1*dis;
    if(dis>=len2-eps) x2=dx,y2=dy;
    else x2 = cx+(dx-cx)/len2*dis,y2 = cy+(dy-cy)/len2*dis;
    return  get_dis(x1,y1,x2,y2);
}
// 三分板子
double three_div(double left,double right){
    // left and right represent the distance gone
    double ans=1e25;
    for(int i=1;i<=1e5;++i){
        double lmid = (2*left+right)/3;
        double rmid = (2*right+left)/3;
        double resl = check(lmid),resr = check(rmid);
        if(resl+eps<=resr) right = rmid;
        else left = lmid;
        ans = min({ans,resl,resr});
    }
    return ans;
}

int main(){
    scanf("%lf%lf%lf%lf",&ax,&ay,&bx,&by);
    scanf("%lf%lf%lf%lf",&cx,&cy,&dx,&dy);
    len1 = sqrt(get_dis(ax,ay,bx,by)),len2 = sqrt(get_dis(cx,cy,dx,dy));
    double ans = min(three_div(0,min(len1,len2)),three_div(min(len1,len2),max(len1,len2)));
    printf("%.12lf\n",sqrt(ans));
}

B. Baby name(字符串:最大字典序子串)

记录:赛后补题
题意:在两个串中分别取子串,拼凑在一起构成新串。要使得这个新串的字典序最大。
思路:分别找到两个串中的字典序最大子串。再试图将他们两个拼凑在一起。
难点:如果暴力找字典序最大子串,复杂度会很高,因此需要一点剪枝操作:即发现,如果当前字母等于出现过的最大字母,而当前上一个字母也等于这个字母。那当前位置必不可能是最大子串的起始位置。
注:这道题如果用char型数组代替string,运行时间可以减半。

其实,这个思路是卡过去的。ababa这样的序列会 O ( n 2 ) O(n^2) O(n2)
正解是SA后缀排序。

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

int get_pos_of_min_string(string s,int len){
    int pos=-1,mx=0;
    for(int i=0;i<len;++i){
        if(s[i]>mx) mx=s[i],pos=i;
        else if(s[i]==mx&&s[i-1]!=mx){
            for(int j=1;j+i<len;++j){
                if(s[pos+j]>s[i+j]) break;
                if(s[pos+j]<s[i+j]){
                    mx=s[i];
                    pos=i;
                    break;
                }
            }
        }
    }
    return pos;
}

int main() {
    string s1,s2;
    cin>>s1>>s2;
    int len1=s1.length(),len2=s2.length();
    char max1=0,max2=0;
    int pos1 = get_pos_of_min_string(s1,len1),pos2 = get_pos_of_min_string(s2,len2);
    printf("%c",s1[pos1]);
    for(int i=pos1+1;i<len1;++i){
        if(s1[i]<s2[pos2]) break;
        printf("%c",s1[i]);
    }
    for(int i=pos2;i<len2;++i) printf("%c",s2[i]);
    puts("");
}

D. Dice(数学:矩阵快速幂+组合数学)

记录:赛后补题

  • 考点:数学 组合数学 + 矩阵快速幂
  • 题意:有n个一样的色子,每个色子的面的数目一样,给出一个数字m。
  • m的含义:每个色子等于m的倍数的面被甩到的概率为0.
  • 求:所有色子甩出的总和等于m的倍数的概率。
  • 思路:容易想到题目只关乎是否是m的倍数,我们把所有面的序号都取模。
  • 得到数组num,记录每个色子中,面的序号取模后等于i的面的个数。
  • 这个概率的分母很容易:即每个色子除却m的倍数(num的i==0)的面之外的其他的面的总数,取n次方。
  • 假若现在只有两个色子,他们的和==0的情况个数newnum[0]=\sum{num[i]*num[m-i]}+num[0]*num[0]
  • 把两个色子得到的和的结果看成一个新的不同的色子。继续将这个色子与第三个色子进行求和。
  • 于是我们想到矩阵快速幂.
  • 矩阵具体的构造:参考大佬博客
    在这里插入图片描述
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 204;
const int MOD = 1e9+7;
int n,k,m,num[N];

ll fp(ll x,ll y){ll ans=1;while(y){if(y&1) ans=ans*x%MOD;x=x*x%MOD;y>>=1;}return ans;}

struct Matrix{
    ll a[210][210];
    Matrix(){memset(a,0,sizeof(a));}
    Matrix cal(const Matrix &A,int n){
        Matrix ans;
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                for(int k=0;k<n;++k){
                    ans.a[i][j]+=a[i][k]*A.a[k][j]%MOD;
                    ans.a[i][j]%=MOD;
                }
        return ans;
    }
};

Matrix fastm(Matrix a,int m,int n){
    Matrix res;
    for(int i=0;i<m;++i) res.a[i][i]=1;
    while(n){
        if(n&1) res=res.cal(a,m);
        a=a.cal(a,m);
        n>>=1;
    }
    return res;
}

ll solve(){
    Matrix ans,pro;   // ans and product
    // draw the matrix
    for(int i=0;i<m;++i){
        int pos = 0;
        for(int j=i;j>=0;--j) pro.a[pos++][i]=num[j];
        for(int j=m-1;;--j){
            if(pos==m) break;
            pro.a[pos++][i]=num[j];
        }
    }
    ans = fastm(pro,m,n);
    return ans.a[0][0];
}

int main(){
    scanf("%d%d%d",&n,&k,&m);
    for(int i=1;i<m;++i) num[i] = k/m;
    int tmp = k%m;
    for(int i=1;i<=tmp;++i) ++num[i];
    ll fm = 0;   // 分母 和 答案
    for(int i=1;i<m;++i) fm += num[i]; // 即一个色子除了%m==0的其余面的数目。
    fm=fp(fm,n);  // 所有色子搭配的总情况数(分母)
    printf("%lld\n",solve()*fp(fm,MOD-2)%MOD);
}

E. Enter to the best problem of this contest!(二叉树)

水题
solver:artist & fashion


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

int main(){
    int n;scanf("%d",&n);
    int u=1,x;
    printf("1\n");fflush(stdout);scanf("%d",&x);
    int left;
    while(x!=0){  // 到当前的距离
        printf("%d\n",u<<1);fflush(stdout);
        scanf("%d",&left); // 询问到左儿子
        if(x>left) u=u<<1;
        else u=u<<1|1;
        x--;
    }
    printf("! %d\n",u);
}

F. Free restricted flights(图论:最短路)

记录:赛后补题
题意:有一张有向图。两个起点。要到同一个点之后返回。这条路径可以有k段免费,整个过程要最短路。

  • 思路:
  • dijkstra分别从两个起点到所有的点。dp记录到这个点,花费多少次免费票,最少的cost
  • 反向建图,再来一次dijkstra。
  • 暴力枚举所有的相遇点,得到结果。
#include<bits/stdc++.h>
using namespace std;
int n,m,a,b,k;
const int INF = 0x3f3f3f3f;

struct edge{
    int v,cost;
};

struct node{
    int u,cost,used;
    bool operator < (const node b)const{
        return cost>b.cost;
    }
};

const int N = 1e5+6;
int dp1[N][11],dp2[N][11],dp3[N][11],dp4[N][11];
vector<edge>G1[N];
vector<edge>G2[N];

void dijkstra(vector<edge>G[],int st,int dp[][11]){
    priority_queue<node> pq;
    for(int i=1;i<=n;++i) for(int j=0;j<=k;++j) dp[i][j] = INF;
    dp[st][0]=0;
    pq.push(node{st,0,0});
    while(!pq.empty()){
        node cur=pq.top();pq.pop();
        for(auto i:G[cur.u]){
            if(dp[i.v][cur.used]>cur.cost+i.cost){
                dp[i.v][cur.used] = cur.cost+i.cost;
                pq.push(node{i.v,dp[i.v][cur.used],cur.used});
            }
            if(cur.used<k&&dp[i.v][cur.used+1]>cur.cost){
                dp[i.v][cur.used+1] = cur.cost;
                pq.push(node{i.v,dp[i.v][cur.used+1],cur.used+1});
            }
        }
    }
    // 能少用票固然少用票。票不必用完
    for(int i=1;i<=n;++i) for(int j=1;j<=k;++j) dp[i][j] = min(dp[i][j],dp[i][j-1]);
}

int main(){
    scanf("%d%d",&n,&m);
    scanf("%d%d%d",&a,&b,&k);
    a++,b++;
    for(int i=0;i<m;++i){
        int u,v,c;scanf("%d%d%d",&u,&v,&c);
        u++,v++;
        G1[u].push_back(edge{v,c});
        G2[v].push_back(edge{u,c});
    }
    dijkstra(G1,a,dp1);
    dijkstra(G1,b,dp2);
    dijkstra(G2,a,dp3);
    dijkstra(G2,b,dp4);
    int ans = INF,pos = -1;
    for(int i=1;i<=n;++i){
        // 枚举相遇点
        if(i==a||i==b) continue;
        int ans1 = INF,ans2 = INF;
        // 从a出发到当前点的最短距离
        // 以及从b出发到当前点的最短距离
        for(int j=0;j<=k;++j){
            // 枚举使用的票数
            ans1=min(ans1,dp1[i][j]+dp3[i][k-j]);
            ans2=min(ans2,dp2[i][j]+dp4[i][k-j]);
        }
        if(ans1+ans2<ans){
            ans = ans1 + ans2;
            pos = i;
        }
    }
    if(pos == -1) puts(">:(");
    else printf("%d %d\n",pos-1,ans);
}

G. Greater dinner(数学:组合数学)

水题
solver:artist


#include<bits/stdc++.h>

using namespace std;
const int MOD = 1e9+7;

int main(){
    int n,m;scanf("%d%d",&n,&m);
    long long ans=1;
    int tmp;
    for(int i=1;i<=m;++i) scanf("%d",&tmp),scanf("%d",&tmp);
    for(int i=1;i<=n;++i) {
        ans=ans*i%MOD;
        if(m&&ans>2&&ans%2==0) ans/=2,m--;
    }
    while(m) ans/=2,m--;
    printf("%lld\n",ans%MOD);
}

H. Happy game(字符串:manacher+hash)

  • 题意:求一个字符串中有多少个本质不同回文子串,且这些子串的长度为奇数
  • 思路:用manacher求出所有奇数长度回文子串然后插入hash统计
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+6;
typedef unsigned long long ull;
int n;
ull seed = 31,base[N],_hash[N],d1[N];
char s[N];
map<ull,int>mp;
int ans = 0;

void init(){
    base[0] = 1;
    for(int i=1;i<N;++i) base[i] = base[i-1]*seed;
}

void makehash(int len,char str[]){
    for(int i=1;i<=len;++i) _hash[i] = _hash[i-1]*seed+(str[i]-'a'+1);
}

// 从i开始,长度为l
ull gethash(int i,int l){
    return _hash[i+l-1]-_hash[i-1]*base[l];
}

void insert(int i,int l){
    if(l==1) return;
    ull tmp = gethash(i,l);
    if(mp.find(tmp)==mp.end()) mp[tmp]=1,ans++;
}

void manacher_odd(char str[]){
    for(int i=1,l=1,r=0;i<=n;++i){
        int k = (i>r)?1:min(d1[l+r-i],r-i);
        while(1<=i-k&&i+k<=n&&str[i-k]==str[i+k]) k++,insert(i-k+1,2*k-1);
        d1[i] = k--;
        if(i+k>r) l=i-k,r=i+k;
    }
}

int main(){
    scanf("%d",&n);
    scanf("%s",s+1);
    init();
    makehash(n,s);
    manacher_odd(s);
    printf("%d\n",ans);
}

K. Katastrophic sort

水题。
solver:david

# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
char s1[maxn],s2[maxn];
int main(){
    scanf("%s",s1+1);
    scanf("%s",s2+1);
    int l1=strlen(s1+1),l2=strlen(s2+1);
    int i;
    bool tag = 0;
    for(i=1;i<=l1&&i<=l2;++i){
        if(s1[i]>='0' && s1[i] <= '9'){
            tag = 1;
            break;
        }
        else{
            if(s1[i] < s2[i]){
                printf("<\n");
                return 0;
            }
            else if(s1[i] > s2[i]){
                printf(">\n");
                return 0;
            }
        }
    }
    if(tag){
        if(l1 > l2){
            printf(">\n");
            return 0;
        }
        else if(l1 < l2) {
            printf("<\n");
            return 0;
        }
        else{
            for(;i<=l1;++i){
                if(s1[i] < s2[i]){
                    printf("<\n");
                    return 0;
                }
                else if(s1[i] > s2[i]){
                    printf(">\n");
                    return 0;
                }
            }
            printf("=\n");
        }
    }
    else{
        printf("=\n");
        return 0;
    }
    return 0;
}

L. Lonely day(图论:BFS+记录路径)

solver:david
code:artist
思路

  • 题意:一个地图,S是起点,E是终点。从S走到E,只能走干净的点。但如果前面(直的)有一串脏点后有一个干净的点,可以一步跳过去。求最短到达E的路径。
  • 思路:因为要记录路径,因此手写队列。注意,同样长度的路径选取字典序小的,那么我们先Down,再left,再right,最后up。注意:数组要开得足够大,路径可能长度为N^2.
#include <bits/stdc++.h>
using namespace std;

const int N = 2e3+5;
int n,m;
int dir[4][2]={
        {1,0},
        {0,-1},
        {0,1},
        {-1,0}
};
bool check(int x,int y){
    return x<=n&&x>=1&&y<=m&&y>=1;
}
char mp[N][N];
int vis[N][N];  // 记录曾经是否走过这个点
int sx,sy;  // start
struct node{
    int x,y;
    int fa;   // 前驱
};
node path[N*N];  // 要记录路径,须手工模拟队列
char ans[N*N];

void print(int pos){
    node cur,last;
    int cnt = 0;
    cur = path[pos];
    pos = cur.fa;
    while(pos!=-1){
        last = path[pos];
        if(cur.x>last.x) ans[cnt++] = 'D';
        if(cur.x<last.x) ans[cnt++] = 'U';
        if(cur.y>last.y) ans[cnt++] = 'R';
        if(cur.y<last.y) ans[cnt++] = 'L';
        pos = last.fa;
        cur = last;
    }
    printf("%d\n",cnt);
    for(int i=cnt-1;i>=0;--i) printf("%c",ans[i]);
}

void BFS(){
    node start,next;
    start.x = sx,start.y = sy,start.fa=-1;
    int front = 0,rear = 0;  // 队列头以及队列尾
    path[rear++] = start;
    vis[sx][sy] = 1;
    while(front<rear){
        start = path[front++];
        for(int i=0;i<4;++i){
            next.x=start.x+dir[i][0],next.y=start.y+dir[i][1];
            if(check(next.x,next.y)&&!vis[next.x][next.y]&&mp[next.x][next.y]!='X'){
                next.fa = front-1;
                path[rear++]=next;
                vis[next.x][next.y]=1;
                if(mp[next.x][next.y]=='E') {
                    print(rear-1);return;
                }
            }
            while(check(next.x,next.y)&&mp[next.x][next.y]=='X')
                next.x=next.x+dir[i][0],next.y=next.y+dir[i][1];
            if(check(next.x,next.y)&&!vis[next.x][next.y]){
                next.fa = front-1;
                path[rear++]=next;
                vis[next.x][next.y]=1;
                if(mp[next.x][next.y]=='E'){
                    print(rear-1);return;
                }
            }
        }
    }
    puts("-1");
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%s",mp[i]+1);
    for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(mp[i][j]=='S') sx = i,sy = j;
    BFS();
}

M. Magic cells(字符串:子序列自动机)

solver: david

  • 题意:s的子序列中,如果有ai的前缀,输出。如果没有,输出impossible
  • 思路:从后往前扫s,记录每个字母在每个位置之后最前出现的position
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
char s[N<<1],a[N<<1];
int n;
int pos[N<<1][28]; // 记录当前位置

void solve(){
    int len = strlen(s+1);
    int nowlast[28];
    for(int i=0;i<26;++i) nowlast[i]=-1;
    for(int i=len;i>=0;--i){
        for(int j=0;j<26;++j) pos[i][j]=nowlast[j];
        nowlast[s[i]-'a']=i;
    }
}

int main(){
    scanf("%s",s+1);
    solve();
    scanf("%d",&n);
    while(n--){
        scanf("%s",a+1);
        int len = strlen(a+1),p = 0;  // 当前在s中所处的位置
        int len2=0;
        for(int i=1;i<=len;++i){
            p=pos[p][a[i]-'a'];
            if(p==-1) break;
            printf("%c",a[i]);
            len2++;
        }
        if(len2) puts("");else puts("IMPOSSIBLE");
    }
}

unsolved

C. Cipher count

挖坑。
题意:至少有多少种不同的字符串(长度小于等于k,字母种类为a种)。
定义不同:短的重复可以得到长的,那么他们相同。
思路:据说是容斥

I. Incredible photography

WA on test 13
思路:
(1)
找左边第一个严格大于他,且连续相同中最左一个的位置:
思路有点像树状数组求逆序对:
先找到位置,再插入i(当前)。
我们把h数组变为INF-h,使得越大的越小。
g数组储存 从小到大,这个(本质为i左边的h从大到小)排序序号的值,且同样的值,储存的位置更小。
查找的时候,lower_bound,即找到第一个大于等于INF-h[i]的值。即第一个小于等于h[i]的值。往左走一格,即第一个严格大于h[i]的值的位置。
las记录这个位置的值在原数组中的位置。
现在考虑把当前插入g:如果las[k]没有数字,或这个位置上的数字不等于当前,就改。因为他必然更大。如果等于的话,不改,因为储存位置更左的值。
(2)用dfs记忆化搜索。

# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
const int INF = 1e9+10;
int n;
int h[maxn];
int g[maxn];
int las[maxn];
int lt[maxn],rt[maxn];
long long dp[maxn];

long long dfs(int pos){
    if(dp[pos]) return dp[pos];
    if(lt[pos]==0&&rt[pos]==n+1) return 0;
    if(lt[pos]==0) return dp[pos]=dfs(rt[pos])+rt[pos]-pos;
    if(rt[pos]==n+1) return dp[pos]=dfs(lt[pos])+pos-lt[pos];
    return dp[pos] = max(dfs(lt[pos])+pos-lt[pos],dfs(rt[pos])+rt[pos]-pos);
}

int main(){
    scanf("%d",&n);
    // 预处理每个点向左向右,第一个严格大于它的,连续相同的取最后一个。
    for(int i=1;i<=n;++i) scanf("%d",h+i),h[i]=1000000001-h[i];
    h[0] = -1;
    g[0] = 0;
    for(int i=1;i<=n;++i) g[i] = INF;
    for(int i=1;i<=n;++i){
        int k = lower_bound(g+1,g+n+1,h[i]) - g;
        lt[i] = las[k-1];
        if(las[k] == 0 || h[las[k]] != h[i]) {
            g[k] = h[i];
            las[k] = i;
        }
    }

    // 找往右第一个严格大于,跟把h数组对调,找往左第一个严格大于没有区别。
    reverse(h+1,h+n+1);
    g[0] = 0;
    for(int i=1;i<=n;++i) g[i] = INF;
    for(int i=0;i<=n;++i) las[i]=0;
    for(int i=1;i<=n;++i){
        int k= lower_bound(g+1,g+n+1,h[i]) - g;
        rt[n-i+1] = n-las[k-1]+1;
        if(las[k] == n || h[las[k]] != h[i]) {
            g[k] = h[i];
            las[k] = i;
        }
    }

    for(int i=1;i<=n;++i) dfs(i);

    for(int i=1;i<n;++i) printf("%lld ",dp[i]);
    printf("%lld\n",dp[n]);

    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值