3.11-3.24周报

寒假训练营2

D

这道题的题意很简单,有k张技能牌,每张技能牌可以把前\(a_i\)张牌放到最下边,消耗\(b_i\)的花费,现在我们需要的牌在从下往上的第k张,要变到第一张,花费最小的方式。建图的思路就有了,边权就是花费,也就是最短路问题,但是边很灵活,每个点都能建出m条边。

点击查看代码
void solve(long long kk) {
    priority_queue<PII,vector<PII>,greater<PII>>q;
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++){
        cin>>a[i]>>b[i];
    }
    for(int i=1;i<=n;i++){
        dist[i]=LLONG_MAX;
    }
    dist[k]=0;
    q.push({0,k});
    while(q.size()){
        auto [dis,dian]=q.top();
        q.pop();
        if(dist[dian]<dis)
            continue;
        for(int i=1;i<=m;i++){
            int jl=a[i],hf=b[i];
            jl+=dian;
            jl%=n;
            if(jl==0)
                jl=n;
            if(dist[jl]>hf+dis){
                dist[jl]=hf+dis;
                q.push({dist[jl],jl});
            }
        }
    }

    if(dist[n]==LLONG_MAX)
        cout<<-1<<endl;
    else
        cout<<dist[n]<<endl;
    return ;
}

codeforces 933 (Div.3)

E

这道题的题意很简单,就是每个位置的水深已知,要在两岸之间建桥,那就需要在一些位置建桥柱,桥柱之间的距离不得超过d,要连续建造k座桥,赛时没出也是因为没注意连续。一眼dp但是要注意不能直接暴力求,会超时,但我们发现,每个位置存的花费其实就是前d个里花费最小的和这个点建桥就可以了,那就不用暴力循环,直接求最小。

点击查看代码

void solve() {
    ve.clear();
    int n,m,K,d;
    cin>>n>>m>>K>>d;

    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        //memset(dp,0x3f,sizeof(dp));
        multiset<int>s;
        dp[1]=1;
        s.insert(dp[1]);
        for(int j=2;j<=m;j++){
            dp[j]=*(s.begin())+a[i][j]+1;
            //cout<<j<<" "<<j-d-1<<" "<<*s.begin()<<" "<<dp[j]<<endl;
            if(j-d-1>0){
                s.erase(s.find(dp[j-d-1]));
            }
            s.insert(dp[j]);
//            for(int k=j-1;k>0;k--){
//                if(j-k-1>d){
//                    break;
//                }
//                dp[j]=min(dp[j],dp[k]+a[i][j]+1);
//            }
        }
        //dp[m]=*s.begin()+a[i][m]+1;
        ve.push_back(dp[m]);
        //cout<<i<<" "<<dp[m]<<endl;
        //cout<<"--------------\n";
    }
    long long cur = 0;
    for (int i = 0; i < K; i++)
        cur += ve[i];

    long long mn = cur;

    for (int i = K; i < n; i++) {
        cur += ve[i] - ve[i - K];
        mn = min(cur, mn);
    }
    cout << mn << endl;
    return ;
}

天梯1

7-6

这题巨简单,但是问题出在了vector的size函数,这个函数的返回值不是int类型,所以要注意可能会出现x<ve.size()-1和x<=ve.size()两个公式不等价的情况。

7-11

这道题大一就做过,当时就没认真补题,现在遇到还是没思路,其实很简单,根据题意可知,这一定是树,也就是跑完树上的几个点之后不用返回起点,那想要最短路就是遍历所有路径之后减去最长的那条路。找最深的结点就是个dfs,那怎么计算出跑完所有点的路径和呢,那就是返回到当前点回到根节点的路径还有多少路径是没有走过的(因为之前的点可能被其他的点走过了,那就不用重复走了)。

点击查看代码
int fa[100010];
int vis[100010];
vector<int>g[100010];
int dp[100010];
void dfs(int x){
    for(auto e:g[x]){
        dp[e]=dp[x]+1;
        dfs(e);
    }
}
void solve(long long kk) {
    int n,m;
    cin>>n>>m;
    int root;
    for(int i=1;i<=n;i++){
        cin>>fa[i];
        if(fa[i]==-1){
            root=i;
        }
        else
            g[fa[i]].push_back(i);
    }
    dp[root]=0;
    dfs(root);
    int max1=0;
    int sum=0;
    for(int i=1;i<=m;i++){
        int x;
        cin>>x;
        max1=max(max1,dp[x]);
        while(vis[x]==0&&x!=root){
            vis[x]=1;
            sum+=2;
            x=fa[x];
        }
        cout<<sum-max1<<endl;
    }
    return ;
}


7-12

这个题其实不难,就是一直不停的套map,因为这个明显也是树,但是可以优化的点是那些老人一定为叶子结点,那我们直接将一个管理机构下的老人数记作这个点的权值,在老人更换机构时就不用考虑边的问题了,直接更换双方的权值即可,求一个管理机构下的老人总数,就是以它为根节点的dfs搜索,权值求和就好,但是有一个段错误,不太懂问题在哪。

点击查看代码

map<pair<string,string>,int>d;
map<string,string>fa;
map<string,vector<string>>ve;
map<string ,int>xx;
int dfs(string s){
    int ans=xx[s];
    for(auto e:ve[s]){
        ans+=dfs(e);
        //cout<<e<<" "<<s<<" "<<ans<<endl;
    }
    return ans;
}
void solve(long long kk) {
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        string s1,s2;
        cin>>s1>>s2;
        fa[s1]=s2;
        ve[s2].push_back(s1);
        if(s1[0]>='0'&&s1[0]<='9')
            xx[s2]++;
//        cout<<i<<" "<<s2<<" "<<xx[s2]<<endl;
//        for(auto e:ve[s2]){
//            cout<<e<<" ";
//        }
//        cout<<"-----\n";
        //fa[s1]=s2;
    }
    while(1){
        string s1;
        cin>>s1;
        if(s1=="E"){
            break;
        }
        if(s1=="T"){
            string s2,s3;
            cin>>s2>>s3;
            string ss=fa[s2];
            xx[ss]--;
            //cout<<ss<<" "<<s2<<" "<<s3<<endl;
            ve[s3].push_back(s2);
            fa[s2]=s3;
            xx[s3]++;
        }
        else if(s1=="Q"){
            string s2;
            cin>>s2;
            int ans=0;
            ans=dfs(s2);
            cout<<ans<<endl;
        }

    }
    return ;
}

codeforces 934(Div.2)

B

这道题题意很简单,就是把2n个数字(而且是1-n每个数字出现两次)左边一半右边一半,每一半都选k个,要求两边的异或值相同,异或有个规律两个相同的数字异或为0,那就很简单了,先选择出现两次的数字,保证异或值为0,不够就拿只出现一次的数字,只要是出现一次的数字那就是两边都会出现一次,那就都选,异或值还一样,但我细节敲错了,wa了好多次。

点击查看代码

int a[100010],b[100010];
set<int>s,sa,sb;
void solve() {
    s.clear();
    sa.clear();
    sb.clear();
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        sa.insert(a[i]);
    }
    for(int i=1;i<=n;i++){
        cin>>b[i];
        if(sa.count(b[i])){
            s.insert(b[i]);
            sa.erase(b[i]);
        }
        else{
            sb.insert(b[i]);
        }
    }
    if(sa.size()>=k){
        int dans=0;
        for(auto e:sa){
            dans++;
            cout<<e<<" "<<e<<" ";
            if(dans==k){
                break;
            }
        }
        cout<<endl;
        dans=0;
        for(auto e:sb){
            dans++;
            cout<<e<<" "<<e<<" ";
            if(dans==k){
                break;
            }
        }
        cout<<endl;
    }
    else {
        int x=sa.size();
        k -= x;
        for (auto e: sa) {
            cout << e << " " << e << " ";
        }

        int dans = 0;
        for (auto e: s) {
            cout << e << " ";
            dans++;
            if (dans == 2*k)
                break;
        }
        cout << endl;
        for (auto e: sb) {
            cout << e << " " << e << " ";
        }
        dans = 0;
        for (auto e: s) {
            cout << e << " ";
            dans++;
            if (dans == 2*k)
                break;
        }
        cout << endl;

    }
    return ;
}

天梯训练赛

I

这道题就是格雷码的生成,规律很简单,二倍延伸时后一半和前一半轴对称,后一半前一位补1,前一半的前一位补0,然后输出第几个是多少,这个题注意范围很大,要用unsigned long long,要注意后一半的时候因为是反过来的,所以要计算一下它是第几个。

点击查看代码
#define int unsigned long long
#define endl "\n"
vector<int>ve;
void solve() {
    int n,k;
    cin>>n>>k;
    int d=pow(2,n-1);
    //cout<<d<<endl;
    string s;
    for(int i=1;i<=n;i++){
        //cout<<k<<" "<<d<<endl;
        if(k>=d){
            //cout<<k<<" "<<d<<endl;
            s=s+'1';
            k=d-(k-d+1);
        }
        else{
            s=s+'0';
        }
        d/=2;
    }
    cout<<s<<endl;
    return ;
}


J

这道题已知每个队的实力和初始得分,按照得分排序,12 34 56这样比赛,比完之后实力值更高的得分+1,一看就是结构体排序,但是tle,我们可以注意,每次组好队之后,一定有n/2个队不得分,他们的相对位置不变,胜的同理,这是有顺序的排序,快排会非常浪费,应该使用归并排序,但是要注意,第一次要快排。

点击查看代码
struct node{
    int cj, sl, id;
}a[200010],w[200010],l[200010];
bool cmp(node x,node y){
    if(x.cj==y.cj){
        return x.id<y.id;
    }
    return x.cj>y.cj;
}

void solve(long long kk) {
    int n,r,q;
    cin>>n>>r>>q;
    for(int i=1;i<=2*n;i++){
        cin>>a[i].cj;
        a[i].id=i;
    }
    for(int i=1;i<=2*n;i++){
        cin>>a[i].sl;
    }
    sort(a+1,a+1+2*n,cmp);
    for(int i=1;i<=r;i++){
        int lose=0,win=0;
        for(int j=1;j<=2*n;j+=2){
            if(a[j].sl>a[j+1].sl){
                a[j].cj++;
                w[++win]=a[j];
                l[++lose]=a[j+1];
            }
            else{
                a[j+1].cj++;
                w[++win]=a[j+1];
                l[++lose]=a[j];
            }
        }
        merge(w+1,w+1+n,l+1,l+1+n,a+1,cmp);
    }
    cout<<a[q].id<<endl;
    return ;
}

Codeforces 935(Div.2)

D

这个题蛮好玩的,就是插队,如果你要插某人的队,那你要给它a的钱,从你到这个人之间的所有人你都要给b的钱,其实就是个贪心,但是要注意,到达m之前的位置,不是必须到m

点击查看代码
int a[200010],b[200010];
void solve() {
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        cin>>b[i];
    }
    int sum=0;
    for(int i=n;i>m;i--){
        sum+=min(a[i],b[i]);
    }
    int sum1=0,min1=LLONG_MAX;
    for(int i=m;i>0;i--){
        min1=min(min1,a[i]+sum1);
        sum1+=min(a[i],b[i]);
    }
    sum+=min1;
    cout<<sum<<endl;
    return ;
}

E

这道题题面就是一个二分查找,按照题意的查找方法写二分很快,但有一个点是没有排序,你最多对现有的顺序进行两次交换,使得不按顺序排序的二分查找依然可以找到这个数。当时就有个想法是先看看不交换的话能找到哪个数,然后直接把这两个数交换,其实没想通为什么,但是想试一下就这么写了一下,还真过了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int a[200010],b[200010];
void solve() {
    int n,x;
    cin>>n>>x;
    int d;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(a[i]==x){
            d=i;
        }
    }
    int l=1,r=n+1,mid;
    while(r-l!=1){
        mid=(l+r)/2;
        if(a[mid]<=x){
            l=mid;
        }
        else{
            r=mid;
        }
    }
    if(d==l){
        cout<<"0\n";
        return ;
    }
    cout<<1<<"\n";
    cout<<d<<" "<<l<<endl;

    return ;
}

posted @ 2024-03-12 23:10  zyzzzzlh  阅读(29)  评论(0)    收藏  举报