2021辽宁省大学生程序设计竞赛(正式赛)

比赛经过:写了七八题,有一个topsort写错地方了,本场题目都较为简单考的知识都比较明显

补题:有些题目还得多思考其实也不难

目录

B.阿强的路

C.传染病统计

D.阿强与网格

E.生活大爆炸

F.Capslock

G.字节类型

H.制造游戏币

I.完美主义

L.神奇的回答

M.比赛!


B.阿强的路

floyd变式

考虑到数据范围图论知识可以联想到floyed,注意题目要的结果是两点之间路径中的最大点权*最大边权最小,我们对于路径a-b就是要找到用了哪些点,跑了哪些边,由于floyd的性质本质就是dp,在用前k个点的时候的最短路,我们不妨按照点权排序去跑第k层,对于当前层次如果边权比我的大,说明在之前有一个边权更小的同时点权最小的答案当前不需要更新答案,本题重复考察了floyd的性质理解

int w[M];
LL g[M][M],f[M][M]; // 两边之间的最短路的最大边权和 两点之间最短路的最大边权*最大点权
struct code{
    int w,id;
    bool operator<(const code&t)const{
        return w<t.w;
    }
}e[N];

void solve(){
    
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>w[i];
        e[i]={w[i],i};
    }
    sort(e+1,e+1+n);
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=g[i][j]=2e18;
    
    while(m--){
        int a,b;LL c; cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b],c);
        f[a][b]=f[b][a]=min(f[a][b],g[a][b]*max(w[a],w[b]));
    }
    
    for(int k=1;k<=n;k++)// 按照点权来排序使用最大的边权
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(g[i][j]>max(g[i][e[k].id],g[e[k].id][j])){ // 当前的大于 [i,e[k].id] 和 [e[k].id,j]
                    g[i][j] = max(g[i][e[k].id],g[e[k].id][j]);
                    f[i][j] = min(f[i][j],g[i][j]*max({w[i],w[j],e[k].w}));
                }
                // 由于我们是按照点权去跑的k如果当前的百年大于i,j的话说明前面有更小的点和更小的边权
            
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) f[i][j]=0;
            if(f[i][j]>1e18) f[i][j]=-1;
            cout << f[i][j] << ' ';
        }
        cout << endl;
    }
    return ;
}

C.传染病统计

暴力

看起来我们需要思考一下性质考虑并查集啥的但是注意到数据范围,我们直接排序之后对于每一个数一直左右跑即可

int a[15];

void solve(){
    
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    sort(a+1,a+1+n);
    
    int smax = 0, smin = n;
    for(int i=1;i<=n;i++){
        int j= i+1;
        int cnt = 1;
        while(j<=n and a[j]-a[j-1]<=2){
            cnt++;
            j++;
        }
        j=i-1;
        while(j>=1 and a[j+1]-a[j]<=2){
            cnt++;
            j--;
        }
        smax=max(smax,cnt);
        smin=min(smin,cnt);
    }
    cout << smin << ' ' << smax << endl;
    return ;
}

D.阿强与网格

数学小推理

对于数据范围我们可以发现肯定是要推出一些小式子的在o(1)的时间复杂度解决这个问题

对于特例可以直接判断,重合的,只有一条线的,我们可以作图模拟一下,当我的点没有抵达n,m同行或者同列的时候从可以直接跳min(n,m)-1到同一层,接着发现如果(n+m)是偶数的话是可以直接跳过去的否则就是必须走一步,对于到了同一层之后我们发现后面的跳和走步数是一样的就是做差,最优解就是 1.一直走过去 2.一直跳过去 3.跳过去之后走

int t,n,m,x,y;

void solve(){
    
    cin>>n>>m>>x>>y;
    if(n==1 and m==1){ // 重合的点直接输出答案
        cout << 0 << endl;
        return ;
    }
    LL res1 = (LL)(n-1+m-1)*x; // 直接一步一步
    if(n==1 or m==1){ // 无法跳跃
        cout << res1 << endl;
        return ;
    }
    int ok = (n+m)%2;
    if(m>n) swap(n,m);
    int X = 1 + m-1 ,Y = m;
    LL res2 = (LL)(m-1)*y;
    LL cnt = n - X; // 步数
    if(cnt==0){ // 直接跳到了
        cout << min(res1,res2) << endl;
        return ;
    }
    if(ok){// 奇数 需要走一步
        res2 += min((LL)(cnt-1)*y+x,(LL)cnt*x);
        cout << min(res1,res2) << endl;
    }
    else{
        res2 += min((LL)cnt*y,(LL)cnt*x);
        cout << min(res1,res2) << endl;
    }

    return ;
}

E.生活大爆炸

小组合数公式

直接使用小范围组合数公式暴力求解即可

LL C[M][M];

void init(){
   for(int i=0;i<M;i++)
       for(int j=0;j<=i;j++)
           if(!j) C[i][j]=1;
            else C[i][j]=C[i-1][j]+C[i-1][j-1];
}
void solve(){
    
    cin>>n>>m>>t;
    LL ans = 0;
    for(int i=4;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(i+j==t){
                ans += (LL)C[n][i]*C[m][j];
            }
        }
    cout << ans << endl;
    return ;
}

F.Capslock

简单小模拟

使用islower(),toupper(),tolower()函数

void solve(){
    
    string s; cin>>s;
    n = s.size(); s=' '+s;
    int ok1 = islower(s[1]),cnt = 0;
    for(int i=1;i<=n;i++){
        if(isupper(s[i])) cnt++;
    }
    
    if(cnt==n){
        for(int i=1;i<=n;i++) s[i]=tolower(s[i]);
    }
    else if(ok1 and cnt==n-1){
        s[1] = toupper(s[1]);
        for(int i=2;i<=n;i++) s[i] = tolower(s[i]);
     
    }
    
    for(int i=1;i<=n;i++) cout << s[i];
    cout << endl;
    
    return ;
}

G.字节类型

大数比较

比较两个大数,直接先数位后大小即可

string s[N]={"127","32767","2147483647","9223372036854775807"," "};
string ans[N]={"byte","short","int","long","BigInteger"};

void solve(){
    
    auto check = [&](string&a,string&b){
        if(a.size()>b.size() or a==b) return true;
        if(a.size()<b.size()) return false;
        n = a.size();
        for(int i=0;i<n;i++){
             if(a[i]==b[i]) continue;
             return a[i]>b[i];
        }
        return true;
    };
    
    string a; cin>>a;
    for(int i=0;i<=4;i++){
        if(i==4 or check(s[i],a)){
            cout << ans[i] << endl;
            return ;
        }
    }
    return ;
}

H.制造游戏币

有限制性的背包

可以发现要求有a的数量一定是大于b,说明,在买a的时候一定是要买b的有一个绑定的性质

我们可以建图来确定绑定关系,明显的如果说有环的话一定是无解的,他那个是注意到对于这个绑定性质可以使用dfs去跑,变成选这个物品要有的真实连带花费

int dp[N],p[N];
LL w[N];
bool in[N];
vector<int> g[N];
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
void dfs(int u){
    for(auto&v:g[u]){
        w[v] += w[u];
        dfs(v);
    }
    if(!g[u].empty()) T -= w[u];// 表示严格的大于关系
}
void solve(){
    
    cin>>n>>m>>T;
    for(int i=1;i<=n;i++) cin>>w[i],p[i]=i;
    bool ok = false;
    while(m--){
        int a,b; cin>>a>>b;
        in[b]=true;
        g[a].push_back(b);
        int fa = find(a), fb = find(b);
        if(fa==fb){
            ok = true;
        }
        else{
            p[fa]=fb;
        }
    }
    if(ok){// 表示矛盾了
        cout << 0 << endl;
        return ;
    }
    for(int i=1;i<=n;i++) if(!in[i]) dfs(i);
    
    dp[0] = 1;
    for(int i=1;i<=n;i++)
        for(int j=w[i];j<=T;j++){
             dp[j] += dp[j-w[i]];
             dp[j] %= mod;
        }
    cout << dp[T] << endl;
           
    return ;
}

I.完美主义

线段树

明显的直接使用线段树维护即可,bool表示当前区间是否可行,同时维护每个区间的左右端点数用于两个区间结合的时候的判断

int w[N];
struct code{
    int l,r;
    int lx,rx;
    bool ok;
}tr[4*N];

void pushup(code&u,code&l,code&r){
     if(!l.ok or !r.ok) u.ok = false;
     else{
         if(l.rx<=r.lx){
             u.ok = true;
         }
         else{
             u.ok = false;
         }
     }
     u.lx = l.lx,u.rx = r.rx;
}

void pushup(int u){
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}

void build(int u,int l,int r){
    if(l==r){
        tr[u]={l,r,w[l],w[l],1};
        return ;
    }
    tr[u]={l,r};
    int mid=l+r>>1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
    pushup(u);
}

void modify(int u,int v ,int x){
    if(tr[u].l== v && v==tr[u].r){
        tr[u].lx = x ;
        tr[u].rx = x;
        return ;
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(v<=mid) modify(u<<1,v,x);
    if(v>mid) modify(u<<1|1,v,x);
    pushup(u);
}

code query(int u,int l,int r){
    if(tr[u].l>=l && tr[u].r<=r){
        return tr[u];
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(r<=mid) return query(u<<1,l,r);
    else if(l>mid) return query(u<<1|1,l,r);
    else{
        code res;
        code ll=query(u<<1,l,r);
        code rr=query(u<<1|1,l,r);
        pushup(res,ll,rr);
        return res;
    }
}
void solve(){
    
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i];
    build(1,1,n);
    
    while(m--){
        int op,l,r; cin>>op>>l>>r;
        if(op==1){
            modify(1,l,r);
        }
        else{
            code t = query(1,l,r);
            cout << (t.ok ? "Yes" : "No") << endl;
        }
    }
    return ;
}

L.神奇的回答

简单模拟

void solve(){
     
    int x; cin>>x;
    if(x<18) cout << x << endl;
    else cout << 18 << endl;
    return ;
}

M.比赛!

topsort

按照题目映射一下直接跑topsort即可

int idx;
vector<int> g[N];
int in[N];
int q[N];
bool use[M][M];
void solve(){
     
    cin>>n;
    while(n--){
       char a,op,b,c;
       cin>>a>>op>>b>>c;
       if(!mp.count(a)){
           mp[a]=++idx;
           G[idx]=a;
       }
       if(!mp.count(b)){
            mp[b]=++idx;
            G[idx]=b;
       }
       if(!mp.count(c)){
            mp[c]=++idx;
            G[idx]=c;
       }
        int A = mp[a], B = mp[b] , C = mp[c];
        in[A]++,in[C]++;
        g[B].push_back(A);
        g[A].push_back(C); 
    }
    vector<int> ans;
    auto topsort = [&](){
        queue<int> q;
        for(int i=1;i<=idx;i++) if(!in[i]) q.push(i);
        while(!q.empty()){
            int u = q.front(); q.pop();
            ans.push_back(u);
            for(auto&v:g[u]){
                if(--in[v]==0) q.push(v);
            }
        }
        return (int)ans.size() == idx;
    };
    if(topsort()){
        for(auto&v:ans) cout << G[v];
        cout << endl;
    }
    else{
        cout << "No Answer" << endl;
    } 
    return ;
}

2021辽宁省数学建模竞赛a题要求我们考虑一个城市的公交车线路交叉口的信号灯优化问题。题目中给定了一些交叉口的信息,包括交叉口的位置、各个方向的车流量以及信号灯的周期等。 首先,我们需要确定各个交叉口的车流量和交通流状况,可以根据给定的数据计算出每个交叉口每个方向的车流量。然后,我们可以利用交通流量的大小和方向来确定信号灯的优化策略。 在优化信号灯的策略中,可以考虑两个方面的因素:最小等待时间和最大通行能力。为了减少交通等待时间,我们可以根据车流量的大小来调整信号灯的周期。对于车流量大的方向,可以适当延长信号灯的绿灯时间,从而减少等待时间。对于车流量相对较小的方向,可以适当缩短信号灯的绿灯时间,以提高整体的交通效率。 另外,为了提高交叉口的通行能力,我们还可以考虑设置不同方向的转弯信号灯。对于车流量较大的方向,可以适当延长直行信号灯的绿灯时间,并设置转弯信号灯来疏导车流。这样可以有效地提高交叉口的通行能力,减少交通堵塞。 在进行信号灯优化的时候,还需要考虑交叉口的安全性。我们可以根据交通流量和车速来合理安排红灯时间,以确保车辆有足够的时间安全通过交叉口。 综上所述,对于2021辽宁省数学建模竞赛a题,我们可以通过对交叉口车流量和通行能力的分析,采取合适的信号灯优化策略,从而减少交通等待时间,提高交叉口的通行能力,并确保交通安全。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值