[EOJ]2019 ECNU XCPC March Selection #1

rank 2

solved 3

第二次训练赛(选拔赛)了,自闭了一个多小时。思维题做得还可以,主要还是好多知识点没认真学过,比如博弈和字符串,很简单的一道Trie题也做不了。近期主要多学学新算法,还有之前草草看的算法都重新学一遍。

A(博弈游戏)

博弈 没学过 GG

(开始看全场都在试A题,自己看了半天没思路还很慌,结果最后没一个人做出来)

unsolved

B(数学,递归)

题意:有N个人过河,每个人有过河时间t,只有一艘能装两个人的船,两个人一起过河时花费的时间为他们之中较长的时间,求最短过河时间。

考虑最慢的两个人过河,有两种方法

1.最快的一个人分两次带他们过河,再自己回来 总时间为t[1]*2+t[n]+t[n-1]

2.最快的两个人过河,然后最快的一个人回来,最慢的两个人过河,然后第二快的人回来 总时间为t[1]+t[2]*2+t[n]

选择用时较少的方案然后递归求解

注意特判n=1的情况

(这题好像挺眼熟的,但场上还是推了半天)

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


typedef long long LL ;

#define rep(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define st first
#define nd second
#define mp make_pair()


const int N = 1e5+7;
const int MX = 1e9+7;
LL a[N];

int n;

LL solve(int x){
    if(x==1)return a[1];
    if(x==2)return a[2];
    if(x==3)return a[1]+a[2]+a[3];
    LL t=min(a[1]+a[2]*2+a[x],a[1]*2+a[x]+a[x-1]);
    return t+solve(x-2);
}



int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    sort(a+1,a+1+n); 
    printf("%lld",solve(n));
} 
View Code

1:12(2A) 

C(贪心)

题意:给出一个n个节点的有向图,每个点有且仅有一条出边,找出一个最大的点集,使得集合里的每一个点都至少是一条起点为集合外的点的有向边的终点。

入度为0的点显然不能选入集合,则该点出边的终点一定可以被选入集合,然后再下一个点不选,这样贪心处理每一条链。

之后剩下若干个环,其中有些点已经被选入集合,依旧贪心选取即可。

通过拓扑排序实现。

(考场上看了一眼理解错题意了就没继续想,最后也没时间做了)

upsolved

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


typedef long long LL ;

#define rep(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define st first
#define nd second
#define mp make_pair
#define pii pair<int ,int >

const int N = 1e6+7;
const int MX = 1e9+7;

int n,ans,cnt;

int to[N],in[N],topo[N],is[N],vis[N];

void dfs(int x,int flag){
    vis[x]=1;
    if(!is[x])flag++;
    if(flag==2)is[x]=1,flag=0;
    if(is[x])flag=0;
    if(vis[to[x]])return;
    dfs(to[x],flag);
}

int main(){
    cin>>n;
    
    rep(i,n){
        cin>>to[i];
        in[to[i]]++;
    }
    
    queue<int> q;
    
    rep(i,n){
        if(!in[i])q.push(i);
    }
    
    while(!q.empty()){
        int h=q.front();
        vis[h]=1;
        q.pop();
        topo[++cnt]=h;
        if(--in[to[h]]==0)q.push(to[h]);
    }

    rep(i,cnt){
        int u=topo[i];
        if(is[u])continue;
        is[to[u]]=1;
        
    }
//    rep(i,n)if(is[i])cout<<i<<endl;
    rep(i,n){
        if(!vis[i]){
            dfs(i,0);
        }
    }
    
    rep(i,n)if(is[i])ans++;
        
    cout<<ans;
} 
View Code

D(trie)

题意:给出一棵有边权的树,求树上的最大异或路径。

以1号节点作为根节点,由于两个相同的数异或为0,xor(a-b)=xor(1-a)^xor(1-b),转化为求两条从根出发的路径,使他们的异或和最大。

因为二进制中某一位代表的数比低位的所有数加起来还大,因此要使异或和最大,可以贪心的使高位尽量为1。

把所有从根节点出发的路径异或和转为01串,从高位到低位插入到Trie中,对于每条路径在Trie中贪心地向下走,每一步尽量使当前为异或结果为1,更新答案即可。

(当时甚至还不会trie...)

upsolved

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


typedef long long LL ;

#define rep(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define st first
#define nd second
#define mp make_pair
#define pii pair<int ,int >

const int N = 1e5+7;
const int MX = 1e9+7;

vector<pii> e[N];

int val[N][32];

int vis[N];

int n,cnt,ans;

struct Trie{
    int v[N*33],ch[N*33][2];
    int sz;
    void init(){
        sz=1;
        memset(ch[0],0,sizeof(ch[0]));
    }

    void insert(int* a){
        int u=0;
        for(int i=0;i<31;i++){
            if(!ch[u][a[i]]){
                memset(ch[sz],0,sizeof(ch[sz]));
                v[sz]=0;
                ch[u][a[i]]=sz++;
            }
            u=ch[u][a[i]];}
        v[u]=1;
    }

    int cal(int* a){
        int u=0,res=0;
        for(int i=0;i<31;i++){
            if(ch[u][!a[i]]){
                res+=1<<(30-i);
                u=ch[u][!a[i]];
            }
            else u=ch[u][a[i]];
        }
        return res;
    }

}trie;

void makestr(int x,int node){
    for(int i=0;i<31;i++){
        if((1<<(30-i))<=x){
            val[node][i]=1;
            x-=(1<<(30-i));
        }
    }
    //for(int i=0;i<31;i++)printf("%d",val[node][i]);
}


void dfs(int node,int res){
    vis[node]=1;
    makestr(res,node);

    for(int i=0;i<e[node].size();i++){
        if(!vis[e[node][i].st])dfs(e[node][i].st,res^e[node][i].nd);
    }
    trie.insert(val[node]);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        e[u].pb(mp(v,w));
        e[v].pb(mp(u,w));
    }


    trie.init();
    dfs(1,0);
    rep(i,n)ans=max(ans,trie.cal(val[i]));
    printf("%d",ans);
}
View Code

E(思维)

题意:数轴上有n个点的,每个点向最近的点(左边优先)有一条有向边,选取尽量少的点为起点,从这些点出发可以遍历所有点。

入度为0的点必须选,之后还没遍历到的点一定成环,显然只可能是相邻两个点形成的二元环,每个环选一个点即可。

(这是道签到题,考场上想麻烦了乱搞过的,浪费了些时间,不应该)

1:43 (1A)

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


typedef long long LL ;

#define rep(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define st first
#define nd second
#define mp make_pair
#define pii pair<int ,int >

const int N = 1e2+7;
const int MX = 1e9+7;

int n,vis[N],to[N],a[N],cnt,tag[N];

pii l[N];

void add(int x,int y){
    to[x]=y;
}

int dfs(int x){
    vis[x]=1;
    if(vis[to[x]]){
        return x;
    }
    dfs(to[x]);
}

int cmp(pii a,pii b){
    if(a.st!=b.st)return a.st<b.st;
    return a.nd>b.nd;
}

int main(){
    scanf("%d",&n);
    rep(i,n)scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for(int i=2;i<=n-1;i++){
        if(a[i]-a[i-1]<=a[i+1]-a[i])add(i,i-1);
        else add(i,i+1);
    }
    add(1,2);
    add(n,n-1);
    rep(i,n){
        memset(vis,0,sizeof(vis));
        int x=dfs(i);
        if(x>=i)l[++cnt]=mp(i,x);
        else l[++cnt]=mp(x,i);
        }
    int ans=cnt;
//    rep(i,ans)cout<<l[i].st<<" "<<l[i].nd<<endl;
    sort(l+1,l+cnt+1,cmp);
//    rep(i,ans)cout<<l[i].st<<" "<<l[i].nd<<endl;
    for(int i=1;i<cnt;i++){
    if(tag[i])continue;
    for(int j=i+1;j<=cnt;j++){
        if(tag[j])continue;
        if(l[i].nd>=l[j].nd){
            tag[j]=1;
            ans--;
        }
    }}
//    rep(i,cnt)if(!tag[i])cout<<l[i].st<<" "<<l[i].nd<<endl;
    cout<<ans;
} 
View Code

upsolved

#include <bits/stdc++.h>

using namespace std;

typedef long long int LL;

#define st first
#define nd second
#define pb push_back
#define mp make_pair
#define pll pair <LL, LL>
#define pii pair <int, int>
#define rep(i,x) for(int i=1;i<=x;i++)

const int N = 1e2+7;
const int MX = 1e9+7;
const LL INF = 1e18+9LL;
const int mod = 20071027;

int n; 

int a[N],to[N],vis[N],in[N];

int main(){
    scanf("%d",&n);
    rep(i,n)scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for(int i=2;i<n;i++){
        if(a[i]-a[i-1]<=a[i+1]-a[i]){
            to[i]=i-1;
            in[i-1]++;
        }
        else {
            to[i]=i+1;
            in[i+1]++;
        }
    } 
    in[2]++;
    in[n-1]++;
    to[1]=2;
    to[n]=n-1;
    
    int ans=0;
    rep(i,n){
        if(in[i]==0){
            ans++;
            int u=i;
            while(!vis[u]){
            vis[u]=1;
            u=to[u];
        }}
    }
    rep(i,n-1)if(!vis[i]&&!vis[i+1])ans++,i++;
    printf("%d",ans);
}
View Code

F(贪心)

题意:数轴上有n个点,m条线段,选出尽量多的线段,使每条线段都能独立地占有至少一个点(不可公用同一个点)。

线段覆盖问题,按右端点升序,右端点相同时左端点降序排序,画图可以看出前边的线段应该尽量用前边的点,把点排序,依此对每条线段二分查找可以用的点。

(考场上开始没想清楚,用左端点做了第一关键字,后来好好画图很容易看出来应该用右端点做第一关键字)

2:46 (5A)

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


typedef long long LL ;

#define rep(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define st first
#define nd second
#define mp make_pair
#define pii pair<int ,int >

const int N = 2e4+7;
const int MX = 1e9+7;

int t[N],n,m,tag[N];

pii a[N];

int cmp(pii a,pii b){
    if(a.nd!=b.nd)return a.nd<b.nd;
    return a.st>b.st;
}

int find(int x){
    int l=1,r=n,ans=n+1;
    while(l<=r){
        int mid=(l+r)/2;
        if(t[mid]>=x){
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    return ans;
}

int main(){
    scanf("%d%d",&n,&m);
    rep(i,n)scanf("%d",&t[i]);
    rep(i,m)scanf("%d%d",&a[i].st,&a[i].nd);
    sort(t+1,t+1+n);
    sort(a+1,a+1+m,cmp);
    int ans=0;
    rep(i,m){
        int x=find(a[i].st);
        while(tag[x]&&x<=n)x++;
        if(x==n+1)continue;
        if(a[i].nd>=t[x]){
            ans++;
            tag[x]=1;
        }
    }
    cout<<ans;
} 
View Code
原文地址:https://www.cnblogs.com/xutianshu/p/10446734.html