SMU Autumn 2024 div2 1st


The First Week

长江后浪推前浪, 浮事新人换旧人。 ————刘斧

一、前言


二、算法

1.逆序对

存在1<=i<j<=n,A[I]>A[j]的有序对叫作逆序对。
一些简单朴素算法的时间复杂度都是O(n*n),包括插入排序冒泡排序等等。
在这里插入图片描述

归并排序

  1. 二分一直二分到只有一个元素为止
  2. 可以想象在二分时,逆序对有以上三种结果,全在左边,全在右边,一左一右
  3. 全在左右的都不容易计算,所以继续归并排序
  4. 如果在二分到仅剩一个元素时进行排序,那么当回来计算归并排序时,左右俩边应当是分别排序好的数组
  5. 开始寻找逆序对个数,分别把左右俩个区间的最小值设置成i,j。当找到一个a[i]>a[j]时,前半部分i后面的元素都将与j形成逆序对。
  6. 剩下的没有被扫的元素也得按顺序存入数组b,在最后赋值给a。
int ans = 0;
//储存逆序对个数
void gbsort(int l,int r,int a[]) {
    int mid = (l+r)/2;
    if(l == r) return ;
    //二分到只有一个就终止
    gbsort(l,mid,a);
    gbsort(mid+1,r,a);
    //先计算俩边的逆序对
    int i = l,j = mid+1;
    int k = 0;
    int t = l;
    while(i <= mid && j <= r) {
        if(a[i] > a[j]) {
            ans += (mid+1-i);
            b[t++] = a[j++];
        }
        else b[t++] = a[i++];
    }
    while(i <= mid) b[t++] = a[i++];
    //右边已经跑完了,左边的有剩余
    while(j <= r) b[t++] = a[j++];
    for (int i = l; i <= r; i++) {
        a[i] = b[i];
    }//必须排序
    return ;
}

树状数组

<1>(2024牛客国庆集训派对day2 I)

在这里插入图片描述
依然不是我写的代码,树状数组没太学明白,基本都是抄模版。over。
题解:
给定一个数组a,包括不重复的1到n的数字,可以给任意多个数字加上1,求最小的逆序对数目。
先求出逆序对的个数,然后查找每个数字的位置,如果i+1的位置在i的位置的前面,就把i变成i+1,并跳过i+1的这一个操作。
代码:

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

#define int long long

int n;
int b[200005];

int t[200005]={0};
typedef struct node{
    int val,ind;
}Node;

Node stu[200005];
int Rank[200005];

int lowbit(int x){
    return x&(-x);
}

void add(int pos){
    for(int i=pos;i<=n;i+=lowbit(i)) t[i]+=1;
}

int ask(int pos){
    int ans = 0;
    for(int i=pos;i;i-=lowbit(i)) ans += t[i];
    return ans;
}

int cmp(Node a,Node b){
    if(a.val == b.val) return a.ind<b.ind;
    return a.val<b.val;
}

signed main(){
    int ans = 0;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>stu[i].val;
        b[stu[i].val]=i;
        stu[i].ind = i;
    }
    sort(stu+1,stu+n+1,cmp);
    for(int i=1;i<=n;i++) Rank[stu[i].ind] = i;
    for(int i=1;i<=n;i++){
        int pos = Rank[i];
        ans += ask(n)-ask(pos);
        add(pos);
    }
    //cout<<ans;
    for(int i=1;i<n;i++){
        if(b[i+1]<b[i]) ans--,i++;
    }
    cout<<ans;
    return 0;
}

2.图论

<1>(2024牛客国庆集训派对day2 F)

这题其实不是图论,还是博弈论,但对我们队来说挺崩溃的吧,就还是归在图论板块里了。
Alice和Bob又要玩游戏了…

题解:
给定n个节点m条线的图,可以选择删去一个联通块(不能自成环),或者删去一条线。
找到所有的联通块个数(包括单独点)和成环的联通块(判断环内线段的数目)…反正是一个繁琐且容易出错的思考过程,最后对这个个数判断奇偶。
但是!删边的时候边的数目-1,删联通块的时候,点的个数比边的个数多一,那么就不形成环,所以每次进行操作奇偶都会改变,直接判断点和边的和的奇偶性即可。
代码:
正解的代码真的很简单,我就不放了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=110;
int n,m;
vector<int>a[N];
vector<int>ans;
int dev[N];
bool st[N];
bool vis[N];
int k=0;
int an=1;
int res=0;
bool cmp(int x,int y){
    return x>y;
}
vector<int>b;
void dfs2(int x,int fa){
//     cout<<x<<endl;
    b.push_back(x);
    vis[x]=1;
    if(a[x].size()==1&&a[x][0]==fa) {    
        return;
    }
    for(int i=0;i<a[x].size();i++){
        if(a[x][i]==fa) continue;
        if(vis[a[x][i]]) {
            k=1;
            continue;
        }
        an++;
        dfs2(a[x][i],x);
    }
}
int findf(int x){
    int sumn=0;
    for(int i=0;i<b.size();i++){
        if(!st[b[i]]){
//             cout<<b[i]<<endl;
            for(int j=0;j<a[b[i]].size();j++){
                if(!st[a[b[i]][j]]) sumn++;
            }
        }
    }
    return sumn/2;
}
int find(int x){
    int num=0;
    queue<int>q;
    for(int i=0;i<b.size();i++){
        if(dev[b[i]]==1){
            q.push(b[i]);
            st[b[i]]=1;
            num++;
        }
    }
    while(q.size()){
        int xx=q.front();
        q.pop();
        for(int i=0;i<a[xx].size();i++){
            dev[a[xx][i]]--;
            if(dev[a[xx][i]]==1) {
                num++;
                st[a[xx][i]]=1;
                q.push(a[xx][i]);
            }
        }
    }
//     cout<<findf(x)<<" "<<an-num<<endl;
    return findf(x)-an+num;
}
void solve(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        a[x].push_back(y);
        a[y].push_back(x);
        dev[x]++;
        dev[y]++;
    }
    int tip=0;
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            dfs2(i,0);
//             cout<<i<<" "<<an<<endl;
            if(!k){
                b.clear();
                ans.push_back(an);
            }
            else {
//                 cout<<find(i)<<endl;
                res+=find(i);
                tip=1;
            }
            k=0;
            an=1;
        }
    }
//     cout<<res<<endl;
    if((ans.size()+res)%2==0) cout<<"Bob"<<endl;
    else cout<<"Alice"<<endl;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

3. 二分

<1>(AcWing 102. 最佳牛围栏)

在这里插入图片描述

题解:
N块土地,要求至少F块地的牛的平均值可能的最大值是多少。
代码:

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define endl "\n"
#define PII pair<int,int>
int n,m;
const int N = 1e5+10;
int c[N],sum[N];
double l,r;

bool check(double x) {
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i-1]+c[i]-x;
    }
    double mi = 0;
    for (int i = 0, j = m;j <= n; j++,i++) {
        mi = min(mi,sum[i]);
        if(sum[j]-mi >= 0) return true;
    }return false;
}

void solve() {
    cin >> n >> m;
    l = 0,r = 0;
    for (int i = 1;i <= n; i++) {
        cin >> c[i];
        r = max(r,(double)c[i]);
    }
    while(r-l > 1e-5) {
        double mid = (l+r)/2;
        if(check(mid)) l = mid;
        else r = mid;
    }cout << r*1000 << endl;
    return ;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
 //   cin >> t;
    while(t--) {
        solve();
    }
    return 0;
}

<2>(2024牛客国庆集训派对day2 J)

题解:
给定数组a,b,矩阵对应位置大小是a[i]+b[j],要求取出一个行不小于x,列不小于y的矩阵的平均值最大。推算可发现应当要分别找出a,b中连续区间平均值最大的区间。
代码:

#include<bits/stdc++.h>

using namespace std;
#define int long long
#define double long double
int n,m,z,y;
const int N = 1e5+10;
int a[N],b[N];
double sum[N];
double ans;

bool check(double x,int c[],int jx,int n) {
    sum[0] = 0;
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i-1]+c[i]-x;
    }
    double mi = 1e9;
    for (int i = 0,j = jx; j <= n; i++,j++) {
        mi = min(mi,sum[i]);
        if(sum[j]-mi >= 0) return true;
    }return false;
}

void solve() {
    cin >> n >> m >> z >> y;
    double mia=0,maa=0,mib=0,mab=0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        maa=max(maa,(double)a[i]);
    }
    while(maa-mia > 1e-8) {
        double mid = (mia+maa)/2;
        if(check(mid,a,z,n)) mia = mid;
        else maa = mid;
    }
    for (int i = 1; i <= m; i++) {
        cin >> b[i];
        mab=max(mab,(double)b[i]);
    }
    while(mab-mib > 1e-8) {
        double mid = (mib+mab)/2;
        if(check(mid,b,y,m)) mib = mid;
        else mab = mid;
    }
    ans = maa+mab;
    printf("%0.10Lf",ans);
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t--) {
        solve();
    }
    return 0;
}

4. dijkstra

我真的会疯,反正思路还行,dij+dp的感觉,但写起来我真的是框框改。

<1>(牛客小白月赛102 D)

在这里插入图片描述
题解:
tip:花费的时间不要看作边权,当作点权处理。
由于k的范围比较小,在dij的时候记录已经有几次不休息,然后把这次休息和不休息的俩种状态都放进去,如果已经k次不休息,这次必须休息,传入的时候把不休息次数的状态也传入。

代码:

#include<bits/stdc++.h>

using namespace std;
#define int long long
typedef pair<int,int> PII;
const int INF = 1e18;
//不可以写0x3f,这是63,要么就多写几个3f
const int N = 1e6;
int n,m,k;

void solve() {
  //  init();
    int a[N];
    cin >> n >> m >> k;
    vector<int>edge[n+10];
    vector<vector<int>>dist(k+10,vector<int>(n+10,INF));
    vector<vector<int>>vis(k+10,vector<int>(n+10,0));
    //不可以在外面开数组范围N,会内存超限
    //好好学一下怎么开二维数组的大小
 //   int dist[k+1][N+1],vis[][2*N];
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 0; i < m; i++) {
        int u,v;
        cin >> u >> v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    int x = 1;
    priority_queue<pair<int,PII>,vector<pair<int,PII>>,greater<pair<int,PII>>>pq;
    //因为我确实不太会放三个数据,所以就这样放吧
    if(k >= 1) {
        dist[1][1] = 1;
        pq.push({1,{1,1}});
    }//如果是0的话就不能放,因为每次都得休息
    dist[0][x] = a[1];
    pq.push({a[1],{1,0}});
    while(!pq.empty()) {
        x = pq.top().second.first;
        int y = pq.top().second.second;
        int z = pq.top().first;
        pq.pop();
        if(vis[y][x]) continue;
    //    cout << x << ' ' <<dist[0][x] << endl;
        vis[y][x] = 1;
        for (int i = 0; i < edge[x].size(); i++) {
            int next = edge[x][i];
            if(y < k) {
            if(dist[y+1][next] > z+1) {pq.push({z+1,{next,y+1}});
            dist[y+1][next] = min(dist[y+1][next],z+1);}
            }//这次休息
            if(dist[0][next] > z+a[next]) {pq.push({z+a[next],{next,0}});
            dist[0][next] = min(dist[0][next],z+a[next]); }//这次不休息
        } 
    }
    int ans = 1e18;
   // cout << dist[]
    for (int i = 0; i<= k; i++) 
        ans = min(ans,dist[i][n]);
    //已经跑到第n次,休息0到k次的最小距离
    cout << ans << endl;
//    cout << "DA" << endl;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--) solve();
    return 0;
}

5.BFS算法

<1>(Required Length)

在这里插入图片描述

题解:
可以想象原来的数字x,给它分别乘上各个位数,构成了第二层,再继续往下推,直到有任何一个的位数满足要求,就可以返回此时的层数,也就是操作次数了。注意不要多次对相同的数字操作,因为它会有很多相同的数字,可以节约时间。
代码:

#include <iostream>
#include <set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>
#include<stack>

using namespace std;
#define int unsigned long long
int n,x;

vector<int>a[30];
bool st[30];
int ans;
int ms = 100;
typedef pair<int,int> PII;
map<int,bool>mp;

int calc(int x) {
    int res = 0;
    while(x) {
        res++;
        x = x/10;
    }
    return res;
}

int js(int x) {
    int res = 0;
    while(x) {
        res = max(res,x%10);
        x = x/10;
    }
    return res;
}

void bfs() {
    int res = 0;
    queue<PII>q;
    q.push({0,x});
    while(q.size()) {
        int a = q.front().first;
        int b = q.front().second;
        q.pop();
        if(mp[b]) continue;
        mp[b] = true;
        if(calc(b) == n) {
            cout << a << endl;
            return ;
        }
        vector<int>ab;
        int xy = b;
        while(xy) {
            ab.push_back(xy%10);
            xy = xy/10;
        }
        for (auto x : ab) {
            q.push({a+1,b*x});
        }
    }
    return ;
}


void solve() {
    cin >> n >> x;
    ans = 0;
 //   cout << calc(x) << ' ' << js(x) << endl;
    if(calc(x) > n || js(x) == 1) {
        cout << -1 << endl;
        return ;
    }
    else if(calc(x) == n) {
        cout << 0 << endl;
        return ;
    }
    else {
        ans = 0;
        bfs();
 //       cout << ms << endl;
    }
}

signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int t=1;
 //   cin >> t;
    while(t--){
        solve();
    }
    return 0;
}

三、总结

累了,不想总结,先这么着吧,题都写不完了已经。
哦不过我写的题还是不止这些的,这些都是我理解了很久的题,就放出来看看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值