题解 | #小沙の好客#

小沙の好客

https://ac.nowcoder.com/acm/contest/46813/A

首先来一段赛前采访:

alt

注意:第三第四指的是第三或者第四简单!

沙烬在2022年寒假集训营(第二场)中立下了50个人AK的flag,然而,只有江莉一人AK(所以一个江莉等于50个人)。

alt

然后评价一下题目:和我想象中的一样毒瘤!

小沙毒瘤!

小沙毒瘤!!

小沙毒瘤!!!

接下来是题解时间!

A 小沙の好客

签到题,前缀和,二分

尽可能取小于 xx 的最大的 kk 个最优。

那么从大到小排序后就是取一段连续的区间,区间求和也就是前缀和。那么每次查询就是从比小于等于 xx 的最多 kk 个商品的价值和,不能取到 kk 个则只能取到最后一个。由于查询数量过多,要用二分进行查找,可以直接 lower_bound 。

注意开 long long ,有笨蛋忘记了。

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LL = long long;

int main(){
    int T = 1;
    while(T--){
        LL n,q;
        cin>>n>>q;
        vector<LL> a(n+1);
        auto s = a;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        a[0] = 2e9;
        a.push_back(0);
        sort(a.begin(), a.end(), greater());
        for(int i=1;i<=n;i++){
            s[i] = s[i-1] + a[i];
        }
        while(q--){
            LL k,x;
            cin>>k>>x;
            int t = lower_bound(a.begin(), a.end(),x, greater()) - a.begin();
            if(t>=n) cout<<0<<endl;
            else{
                int r = min(t+k-1,n);
                cout<<s[r]-s[t-1]<<endl;
            }
        }
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60686036

B 小沙の博弈

签到题,贪心

字典序最小,那每次拿一个石子就可以了,如果共有偶数个石子就是平局,否则小沙会比小雅多一个,小雅赢。

也就是说,这个游戏小沙不可能能赢(这就是家庭弟位吗?建议以后用这个游戏决定谁做家务!)。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int main(){
    int T = 1;
    while(T--){
        int n;
        cin>>n;
        if(n&1) cout<<"Yaya-win!"<<endl;
        else cout<<"win-win!"<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60686386

C 小沙の不懂

中等题,防(工口姐姐)AK(的)题

但其实嘤嘤赛后也被 hack 了,hack 如下:

alt

上图也讲了大致的做法,就是把前导零提取出来后,再加一点特判就行。

假代码就不放了(不懂啊,这我是真的不懂)。

D 小沙の赌气

中等题,STL战神,阅读理解

怎么有人这么菜的?

不用攻略就过不了关的?

怎么还有两个?

怎么两个人玩游戏的?

怎么两个人不一起玩游戏的?

怎么这两个人不玩分手厨房的?

嘤嘤の赌气!

用一个数据结构记录有哪些道具,并按照左端点从小到大排序,然后每次新获得一个道具时,就从前往后看能用哪些道具(一关可以重复过,无所谓的),用完后删除道具,最后到了哪里。

这个数据结构可以选择,map,set,priority_queue 等。

注意:有卧龙凤雏第一关都过不去,所以初始化的时候需要为0,而不是1。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int main(){
    int T = 1;
    while(T--){
        int n;
        cin>>n;
        vector<PII> sj(n+1),yy=sj;
        for(int i=1;i<=n;i++){
            int l,r;
            cin>>l>>r;
            sj[i] = {l,r};
        }
        for(int i=1;i<=n;i++){
            int l,r;
            cin>>l>>r;
            yy[i] = {l,r};
        }
        int a = 0 , b = 0;
        map<PII,int> L,R;
        for(int i=1;i<=n;i++){
            L[sj[i]]++;
            R[yy[i]]++;
            while(L.size()){
                auto [l,r] = L.begin()->first;
                if(l>a+1) break;
                a = max(a,r);
                L.erase(L.begin());
            }
            while(R.size()){
                auto [l,r] = R.begin()->first;
                if(l>b+1) break;
                b = max(b,r);
                R.erase(R.begin());
            }
            if(a==b) cout<<"win_win!"<<endl;
            else if(a>b) cout<<"sa_win!"<<endl;
            else cout<<"ya_win!"<<endl;
            cout<<abs(a-b)<<endl;
        }
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60696966

E 小沙の印章

防AK题(防嘤嘤AK的题),构造,诈骗题

怎么那么喜欢环,裴坚吗?

只要你不知道这是个环,你就会了。。。。。

嘤嘤卡了一个小时,赛后 jls 说了一句冒泡排序,嘤嘤就会了。。。。。

关键是,我一直是 (1 2),(3 4),(5 6);(2 3),(4 5),(6 1) 这样轮流换,然后可以做到用奇数遇见所有偶数。但只要把 (6 1) 的交换去掉,就出结果了QAQ(战死沙场

冒泡排序,冒一遍就可以看出来,确实都碰见了一遍,虽然不知道为什么,然后代码也非常简单。

1 2 3 4 5 6
2 1 4 3 6 5
2 4 1 6 3 5
4 2 6 1 5 3
4 6 2 5 1 3
6 4 5 2 3 1
6 5 4 3 2 1

代码如下:

#include <bits/stdc++.h>

using namespace std;

int main(){
    int T = 1;
    while(T--){
        int n;
        cin>>n;
        cout<<n<<endl;
        vector<int> a(n+2,-1);
        for(int i=1;i<=n;i++){
            a[i] = i;
        }
        for(int i=1;i<=n;i++){
            if(n&1) cout<<n/2<<endl;
            else{
                if(i&1) cout<<n/2<<endl;
                else cout<<n/2-1<<endl;
            }
            for(int i=1;i<=n;i++){
                if(a[i]<a[i+1]){
                    cout<<a[i]<<" "<<a[i+1]<<endl;
                    swap(a[i],a[i+1]);
                    i++;
                }
            }
        }
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60724757

F 小沙の串串

困难题,贪心

题解好难不会写!

首先,由于操作顺序是任意的,所以我们的操作可以当成直接删掉一个字母,在字母池中加入一个字母。等所有操作结束后,再把字母池进行从大到小排序后丢在新字符串末尾。

那么现在我们就需要删除 kk 个字母,使得剩下的字符串字典序最大。

字典序最大的字母挪到最前面挤成一堆肯定是最优的,若都挪完了,那么接下来就是挪动字典序次大的字母。若挪不到(操作次数不足),那么肯定要把第一个挪不到前面的大字母尽可能往前挪,也就是剩下的操作一定要在这个大字母之前进行(根据字典序的定义进行贪心,显然(不会证明,去看毒瘤沙的证明)更优)。

也就是说的剩下的操作只会在把前面挤成一堆的大字母去掉,再把当前的大字母和后面的其他字母去掉后剩下的字符串中进行。剩下的操作方式依旧是删除字母,使得剩下的字典序最大,也就是重复上述操作。

比如,字符串为 cadbcada ,操作次数为 3 ,首先尝试把最大的字母 d 挤成一堆,第一个 d 要往前挪需要操作两次,字符串就会变成 dbcada 。接下来是把第二个 d 挪到前面,需要 3 次操作,但操作次数只剩 1 次,所有剩下 1 次操作我们一定会在 bca 中进行。bca 中的最大字母为 c ,将 c 挪到最前恰好需要 1 次操作,字符串变成 ca ,操作结束。接下来把字符串进行还原,首先最前面堆在一起后被去掉的大字母为 d ,然后中间一段为 ca ,大字母和后面的其他字母为 da ,字母池为 cba ,拼接起来就是 dcadbca 。

但有时候操作次数并不会用完,如上例的字符串,若操作次数为 4 ,最后那个操作进行完之后,我们会发现 ca 是不会进行 1 次操作的,所以我们还需要在剩余的字符串中,从小到大的删除字母,也就是删掉 a ,只保留 c ,这样可以使整体字典序尽可能大(因为后面还有 d 和其他字母)。再按照上例拼接则是 dcdabcaa 。

cadbcada 3
dbcada   ca
d bca da   ca		//前面已固定的  要操作的  后面无影响的  字母池
d ca da  cab
dcadacba

cadbcada 4
dacada   ca
d bca da   ca
d ca da  cab
d c da caba
dcdacbaa

代码如下:

#include <bits/stdc++.h>

using namespace std;

int main(){
    int T = 1;
    while(T--){
        int n,k;
        cin>>n>>k;
        string s;
        cin>>s;
        if(k>=n){
            sort(s.begin(), s.end(), greater());
            cout<<s<<endl;
            continue;
        }
        vector<int> b(n+1,1);			//记录字符是否被删除
        s = "{" + s;
        string t;
        int r = n;
        for(int j='z';j>='b';j--){
            for(int i=1;i<=r;i++){
                if(b[i] && s[i]==j){
                    int l = i;
                    for(;l>0;l--){
                        if(s[l-1]>=j) break;
                    }
                    if(l>0){
                        if(k>=i-l){
                            for(int j=l;j<i;j++){
                                b[j] = 0;
                                t += s[j];
                            }
                            k -= i-l;
                        }
                        else r = i;
                    }
                }
            }
        }
        for(int j='a';j<='z';j++){
            for(int i=1;i<=r;i++){
                if(b[i] && k>0 && s[i]==j){
                    t += s[i];
                    k--;
                    b[i] = 0;
                }
            }
        }
        for(int i=1;i<=n;i++){
            if(b[i]) cout<<s[i];
        }
        sort(t.begin(), t.end(), greater());
        cout<<t<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60708325

G 小沙の编码

防AK题,二分图匹配

我使用的图匹配是网络流,匈牙利算法(牛头人算法)不知道可不可以过。

ai1a_{i-1} 二进制下仅有一位不同的数,最多只有 log2nlog_2n 个,然后把这些数与 ai+1a_{i+1} 进行对比是否也只有一位不同,且没在已知的位置出现过,若满足,则将第 ii 个位置向满足的数字连一条容量为 1 的有向边。

然后从超级源点向所有奇数位置连一条容量为 1 的边,所有没给定的数字向超级汇点连一条容量为 1 的边,然后跑一遍最大流,再找一下割边,把数字填进位置,就可以了。

代码如下:

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

template <typename T>
class Dinic{
public:
    struct edge{
        int to;         //到达点 
        T flow;         //边权流量
        int re;         //反向边下标 
    };
    vector<vector<edge>> ve;
    vector<vector<int>> true_edge;
    vector<int> deep, cur;      //深度,以及弧优化
    int n,S,E;
 
    Dinic(int n,int S,int E) : n(n) , S(S) , E(E){
        ve.resize(n+1);
    }
 
    void add(int u,int v,T w){
        true_edge.pb({u, v, (int)ve[u].size()});    //正向边及下标,用于求最小割
        ve[u].pb({v, w, (int)ve[v].size()});        //正向边
        ve[v].pb({u, 0, (int)ve[u].size()-1});      //反向边
    }
 
    bool bfs(){
        deep = move(vector<int>(n+1,0));            //初始化,因为每次 bfs 后 deep 不一定相同 
        cur  = move(vector<int>(n+1,0));            //弧优化归 0 
 
        queue<int> q;
        q.push(S);
        deep[S] = 1;
        while(q.size()){
            auto u = q.front();
            q.pop();
            if(u==E) return true;                   //可达,则有增广路,存在流量 
            for(auto &[i,j,k] : ve[u]){
                if(deep[i] || !j) continue;
                deep[i] = deep[u] + 1;
                q.push(i);
            }
        }
        return false;
    }
 
    T dfs(int u,T in){
        if(u==E) return in;                         //增广路最终的有效流量 
 
        T out = 0;                                  //流出的流量
        for(int i=cur[u];i<ve[u].size();i++){       //从还有流量的边开始走
            auto &[v,flow,re] = ve[u][i];
            cur[u] = i;                             //更新弧优化
            if(!flow || deep[v]!=deep[u]+1) continue;
            T res = dfs(v , min(in,flow));          //向下一层流量限制为获得的流量和可输出的流量中小的那一个
            flow -= res;                            //本条边可输出的流量减少了 res
            ve[v][re].flow += res;                  //反向路径可输出流量增加了 res
            in -= res;                              //获得的流量消耗了 res
            out += res;                             //当前结点可输出的流量增加了 res
            if(!in) break;                          //获得流量消耗完了,没必要往下走了
        }
        if(!out) deep[u] = 0;                       //当前结点输出不了流量了,下次不用进来了
        return out;                                 //返回输出的流量
    }
 
    T max_flows(){
        T sum = 0;
        while(bfs()){
            sum += dfs(S,1e9);
        }
        return sum;
    }
 
    vector<pair<int,int>> min_cuts(){
        vector<pair<int,int>> ans;
        for(auto &v : true_edge){               //v[0]~v[2] 分别记录有向边起点,终点,边的下标
            if(!ve[v[0]][v[2]].flow)            //当正向边流量为0时,说明是最小割
                ans.pb({v[0],v[1]});
        }
        return ans;
    }
};
 
int main(){
    int T = 1;
    while(T--){
        int n;
        cin>>n;
        vector<int> a(n+1);
        map<int,int> mp;
        map<int,int> mi;
        for(int i=1;i<=n;i*=2){
            mi[i]++;
        }
        for(int i=0;i<n;i+=2){
            cin>>a[i];
            mp[a[i]]++;
        }
        int S = 0;
        int E = n*2+1;
        Dinic<int> dinic(n*2+2,S,E);
        for(int i=1;i<n;i+=2){
            for(int j=0;j<=20;j++){
                int t = a[i-1] ^ (1<<j);
                if(!mp.count(t) && t<n){
                    if(i+1>=n) dinic.add(i,t+n,1);
                    else if(mi.count(a[i+1]^t)) dinic.add(i,t+n,1);
                }
            }
        }
        for(int i=1;i<=n;i+=2){
            dinic.add(S,i,1);
        }
        for(int i=0;i<n;i++){
            if(!mp.count(n)) dinic.add(i+n,E,1);
        }
        int ans = dinic.max_flows();
        auto v = dinic.min_cuts();
        for(auto &[x,y] : v){
            if(x==S || y==E) continue;
            a[x] = y-n;
        }
        for(int i=0;i<n;i++){
            cout<<a[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60713811

H 小沙の店铺

签到题,模拟,阅读理解,猜题意

你看懂了,再猜一下题意,再模拟一下,就可以了。

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LL = long long;

int main(){
    int T = 1;
    while(T--){
        LL x,y,k,n,T;
        LL ans = 0;
        cin>>x>>y>>k>>n>>T;
        LL sum = 0;
        for(int i=1,j=n;i<=n;i++,j--){
            ans += x*j;
            sum += j;
            x += sum/k*y;
            sum %= k;
            if(ans>=T){
                cout<<i<<endl;
                return 0;
            }
        }
        cout<<-1<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60688830

I 小沙の金银阁

中等题,阅读理解

看了半天,没懂写的什么玩意儿。。。。。。。。。。

要想不亏,那么每一场下注必须是前面所有场的和,那么至少会是 1,1,2,4,8,16.....2n21,1,2,4,8,16.....2^{n-2} 总和为 2n12^{n-1} ,随便整几场就没了,所以大于 51 场一定无解直接不用考虑。并且根据这个式子,第一天要下注 m/2n1m/{2^{n-1}}

考虑优化,那么某一场要赢更多的话,就在这场加 11 ,那么其之后的场次必须也要加 1,2,4,8...1,2,4,8... ,也是指数级别。并且根据题目,前面加优于后面加,所以加的肯定不会太多(每个位置最多就加一次 11 ),直接从前往后暴力就可以。

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LL = long long;

int main(){
    int T = 1;
    while(T--){
        int n;
        LL m;
        cin>>n>>m;
        if(n==1){
            cout<<m<<endl;
            continue;
        }
        if(n==2){
            cout<<m/2<<" "<<m-m/2<<endl;
            continue;
        }
        if(n>51){
            cout<<-1<<endl;
            continue;
        }
        if((1ll<<(n-1))>m){
            cout<<-1<<endl;
            continue;
        }
        LL l = m>>(n-1);
        vector<LL> a(n+1);
        a[1] = a[2] = l;
        LL sum = l*2;
        for(int i=3;i<=n;i++){
            a[i] = a[i-1]<<1;
            sum += a[i];
        }
        for(int i=1;i<=n;i++){
            int t = n - i + 1;
            while(sum+(1ll<<t-1)<=m){
                sum += 1ll<<t-1;
                LL k = 1;
                a[i] += k;
                for(int j=i+1;j<=n;j++){
                    a[j] += k;
                    k *= 2;
                }
            }
        }
        for(int i=1;i<=n;i++){
            cout<<a[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60700696

J 小沙の最短路

防AK题,构造

目前整个寒假集训营唯一防住吉他英雄(波门)AK的题,谁是毒瘤心里有数。

这题是暑假的一道巨hard题转变来的,当时讨论了很久,没有结果,然后小沙搞了个这个简化版,大概知道解法,但一开始犯了个很傻逼的错误,导致初始值搞错了还是构造出了问题,且代码也难写。

转圈圈就可以了,让我想到了一首歌:恋爱脑梗门!)。

(快去看契约之吻和孤独摇滚!)

alt

粉色的是起点(鼠标画画真难受),最短路就是全转一遍,也就是 n21n^2-1

由于一开始构造出错了,导致代码写的比较长,可以看看jiangly的,他的比较短(单纯指代码)。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int main(){
    int T = 1;
    while(T--){
        int n;
        cin>>n;
        vector a(n+10,vector<int>(n+10,-1));
        int x = n/2+1;
        int y = n+1>>1;
        cout<<x<<" "<<y<<endl;
        vector<PII> d = {{0,1} , {-1,0} , {0,-1} , {1,0}};
        int t = 2;
        int now = 2;
        a[x][y] = 0;
        a[x][y+1] = 0;
        a[x-1][y+1] = 1;
        a[x-1][y] = 2;
        x--;
        for(int i=1;i<n*n;i++){
            int ne = (t+1)%4;
            int xx = x + d[ne].first;
            int yy = y + d[ne].second;
            if(a[xx][yy]<0){
                t = ne;
                ne = (t+1)%4;    
            }
            else{
                if((a[xx][yy]+1)%3 == now || a[xx][yy] == now) now = (now+1)%3;
                xx = x + d[t].first;
                yy = y + d[t].second;
            }
            a[x][y] = now;
            x = xx;
            y = yy;
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                cout<<a[i][j]<<" ";
            }
            cout<<endl;
        }
        cout<<n*n-1<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60718436

K 小沙の抱团 easy

签到题,人类智慧

结论就是,每次喊 n2+1\lfloor \frac {n} {2} \rfloor + 1 即可,因为这样留下的人最少。

重复操作,直至人数小于等于 2 即可。

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LL = long long;

int main(){
    int T = 1;
    while(T--){
        LL n;
        cin>>n;
        int ans = 0;
        while(n>2){
            ans++;
            n = n/2+1;
        }
        cout<<ans<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60683945

L 小沙の抱团 hard

中等题,DP

维护剩余 ii 人所需花费的最小代价,由于人数不会变多,所以从大到小枚举转移。

值得注意的是:最后不一定只剩两人,有可能更多!

代码如下:

#include <bits/stdc++.h>

using namespace std;

using LL = long long;

int main(){
    int T = 1;
    while(T--){
        int n,m;
        cin>>n>>m;
        vector<PLL> a(m);
        for(auto &[x,y] : a){
            cin>>x>>y;
        }
        if(n<=2){
            cout<<0<<endl;
            continue;
        }
        vector<LL> dp(n+1,1e18);
        dp[n] = 0;
        for(int i=n;i>=2;i--){
            for(auto &[x,y] : a){
                if(y>=i) continue;
                int t = i/y*y;
                dp[t] = min(dp[t] , dp[i] + x);
            }
        }
        LL ans = 1e17;
        for(int i=2;i<=n;i++){
            if(dp[i]<ans){
                ans = dp[i];
                break;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

提交记录:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60691414

终于写完了,写了四个多小时,累死我了!(感觉不如再打一场比赛)

本来是不打算写的,但是大家都战死沙场了,非常有戏剧性,所以想写一篇吐槽一下(然后就挖下了大坑)。这个比赛结果结合赛前采访,咱又可以迫害小沙一年啦!嘿嘿!

吐槽归吐槽,玩梗归玩梗,迫害归迫害,但小沙出的题还是不错的(不考虑阅读理解的话!),每一题都有坑点,我几乎就没有不罚时的题,11题13发罚时(有笨逼一直忘记 long long ),三个11题的选手恰好各卡一道题。

不知道下下次见到小沙是什么时候,想他!(下次应该是EC,他应该会去的吧,但是没见过他对象,不知道EC会不会一起来)

alt

全部评论
梗小姐可爱! 文章也很可爱!!!
1 回复 分享
发布于 2023-02-02 16:08 江苏
我记得当时讨论的J就是无解,我说过的吧(
点赞 回复 分享
发布于 2023-02-03 13:45 湖北

相关推荐

Pandaileee:校友加油我现在也只有一个保底太难了
点赞 评论 收藏
分享
勇敢的联想人前程似锦:如果我是你,身体素质好我会去参军,然后走士兵计划考研211只需要200多分。
点赞 评论 收藏
分享
27 收藏 评论
分享
牛客网
牛客企业服务