欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

2019年湘潭大学程序设计竞赛(重现赛)题解

程序员文章站 2022-07-15 16:10:07
...

概述

因为湘潭大学的ABCD题目过于简单,我希望大家都有能力完成上述的题目,所以这里我不做详述。所有我重点解答一下后4题的解法。

E-Watermelon

本题的解法就是维护两个值,我用L和R进行表示,L和R初始值都为m,那么很明显我们可以用L表示经过前i个人吃西瓜后所能剩下的最多西瓜数量是多少,那么对于lililalala每次减少对应的a[i]值,但是对于其他人只用减少1即可。而R表示每个人都按自己最大肚量吃所能剩下的最少的值,即每次经过一个人都需要减少对应的a[i]值。那么明显可以得出一个结论,就是如果对于除了lililalala以外的所有人如果L值先一步小于等于0,那么输出NO,如果对于lililalala当前R值更早小于等于0,那么输出YES。

#include <bits/stdc++.h>
using namespace std;
 
const int maxn = 1e5+100;
 
int a[maxn];
 
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        int n, m;
        scanf("%d%d", &n, &m);
        int id = 0;
        for(int i = 0; i < n; i++){
            scanf("%d", &a[i]);
            if(a[i] > a[id]) id = i;
        }
        int L = m, R = m;
        int i = 0;
        while(true){
            if(i == id){
                if(R <= 0){
                    puts("YES");
                    break;
                }
                L -= a[i];
                R -= a[i];
            }
            else{
                if(L <= 0){
                    puts("NO");
                    break;
                }
                L--;
                R -= a[i];
            }
            i = (i+1)%n;
        }
    }
    return 0;
}

有更快的解法吗?

上述程序的时间复杂度是O(m)O(m),那么能继续优化吗??
如果我们把除了lililalala的人看做一个整体呢?那么对于lililalala每次LLRR减少a[i]a[i],对于剩余的n1n-1个人每次L减少n1n-1,每次RR减去n1n-1个人aa数组的值的总和,但是注意维护的开头部分需要注意,因为lililala可能处于队伍的中间,例如:nn个人对应的aa数组的值是:1 2 3 4 5 4 3 2 1,那么我们首先处理前面的1 2 3 4部分,然后按照上述的思想进行处理。代码这里大家自己理解自己重新写一下。

F Black & White

因为题目要求连续的0或者连续的1的最大值,那我们可以将连续的1变为0,或者连续的0变为1即可(注:这里的连续0或者1是指0和0之间可能存在1,但是不能存在0,举个简单的例子01101010,这里面可以第一个,第四个,第六个和第八个0是连续的),一种比较好想的方法是直接二分答案,然后再序列中判断该长度能够通过m次操作得到,时间复杂度O(nlgn)O(n lg n)代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
string s;
int a[maxn],pre0[maxn],pre1[maxn];
int n,m;
bool check(int mid)
{
    for(int i=1;i<=n-mid+1;i++)
        if(pre1[i+mid-1]-pre1[i-1]<=m||pre0[i+mid-1]-pre0[i-1]<=m)
            return true;
    return false;
}
 
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        scanf("%d%d",&n,&m);
        cin>>s;
        for(int i=1;i<=n;i++) a[i]=s[i-1]-'0';
        for(int i=1;i<=n;i++)
            if(a[i]==1)
                pre0[i]=pre0[i-1],pre1[i]=pre1[i-1]+1;
            else
                pre1[i]=pre1[i-1],pre0[i]=pre0[i-1]+1;
        int low=0,high=n,mid,ans=0;
        while(low<=high)
        {
            mid=(low+high)>>1;
            //cout<<mid<<endl;
            if(check(mid))
                ans=mid,low=mid+1;
            else
                high=mid-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

有没有更快的写法呢,当然也是存在的:我们可以将连续的0或者1变为对应1或者0,按照这样的思想顺序进行处理就可以了,时间复杂度为O(n)O(n)更多的细节简单见代码,代码给出了对应的注释:

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

int main()
{
    int t,n,num,k;
    cin>>t;
    while(t--){
        cin>>n>>num;
        scanf("%s",s);
        int ans=0;
        int len=strlen(s);
        int k=0,cnt=0;
        //对头部进行处理
        a[k++]=-1,b[cnt++]=-1;
        // a统计0所在的位置, b统计1所在的位置
        for(int i=0;i<len;i++)
            if(s[i]=='0') a[k++]=i;
            else b[cnt++]=i;
        //对尾部进行处理
        a[k]=len,b[cnt]=len;
        for(int i=0;i<k;i++){
            //这一步表示把从i+1(i=0是作者设的-1,所以统一从i+1开始)到i+num中所有位置中的0都变为1所能达到的最大1的长度,注意不能越界
            int s=min(k,i+num+1);
            // 注意头和尾处理的时候将0变成1,但是i+1位置如果前面是1话还需要把前面的1也考虑进去
            // 同理i+num将0变成1的时候也需要把i+num后面的1也考虑进去,所以代码中作者头部取a[i]+1,尾取a[s]-1
            // 实际从i到s一共包括num+2个0的位置了,还有就是作者头尾的处理方式值得学习。
            ans=max((a[s]-1)-(a[i]+1)+1,ans);
        }
        //下面同理将1变为0.
        for(int i=0;i<cnt;i++)
        {
            int s=min(cnt,i+num+1);
            ans=max((b[s]-1)-(b[i]+1)+1,ans);
        }
        cout<<ans<<endl;
    }
}

G Truthman or Fakeman

这道题的解法就是建图,然后对图进行染色。题目中有2中描述信息,一种是u,v,0 – u认为v是一个Fakeman; 如果u是Truthman,那么v就是Fakeman,那么u和v的身份不同,如果u是Fakeman,那么因为u说假话,那么v是Truthman,u和v的身份仍然不相同。另一种描述信息是:u,v,1 – u认为v是一个Truthman; 这里不做过多分析,类比上述分析,u和v身份相同,所以这样我们就可以对每个人设置一个身份来得到其他人的身份。
图示如下:
2019年湘潭大学程序设计竞赛(重现赛)题解
这里我想大概理解了,上次我教了大家邻接表建图的方法,这里我用的也是这个方法。不会的同学可以问一下会的同学,这里不做过多解释了。给出代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
 
struct Edge{
    int to, val, next;
    Edge(){}
    Edge(int a, int b, int c):to(a), val(b), next(c){}
}e[maxn<<1];
 
char s[maxn];
int head[maxn], tot;
int color[maxn], vis[maxn], num0, num1;
 
bool dfs1(int u){
    bool flag = false;
    for(int i = head[u]; i != -1; i = e[i].next){
        int v = e[i].to;
        if(color[v] == -1){
            if(e[i].val) color[v] = color[u];
            else color[v] = 1-color[u];
 
            if(color[v] == 0) num0++;
            else num1++;
            flag = flag||dfs1(v);
        }
        else if(e[i].val?color[u]!=color[v]:color[u]==color[v]) return true;
    }
    return flag;
}
 
void dfs2(int u, int val){
    vis[u] = 1;
    if(color[u] == val) s[u] = '1';
    else s[u] = '0';
    for(int i = head[u]; i != -1; i = e[i].next){
        int v = e[i].to;
        if(!vis[v]) dfs2(v, val);
    }
}
 
void init(){
    memset(vis, 0, sizeof(vis));
    memset(color, -1, sizeof(color));
    memset(head, -1, sizeof(head));
    tot = 0;
}
 
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        init();
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 0; i < m; i++){
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            e[tot] = Edge(v, w, head[u]);
            head[u] = tot++;
            e[tot] = Edge(u, w, head[v]);
            head[v] = tot++;
        }
        int flag = 0;
        for(int i = 1; i <= n; i++)if(color[i] == -1){
            num0 = 1, num1 = 0;
            color[i] = 0;
            flag = flag||dfs1(i);
 
            if(num0 > num1) dfs2(i, 0);
            else dfs2(i, 1);
        }
 
        if(flag) puts("-1");
        else{
            for(int i = 1; i <= n; i++) printf("%c", s[i]);
            puts("");
        }
    }
    return 0;
}

Chat

一个分组背包问题,关于背包问题网上有一个非常好的教程叫做背包九讲,大家自己去搜索,不懂得群里交流。
这道题预处理的过程非常有意思:我们用mx[i][j]表示第i天与女神通话时间为j不需要上线的时间,预处理之后,讲分组背包的板子网上套就可以了

#include<bits/stdc++.h>
using namespace std;
char s[205];
int dp[205],sum[205],mx[205][205];
 
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        int n, m, h;
        memset(dp, 0, sizeof(dp));
        scanf("%d%d%d",&n,&m,&h);
        memset(mx, 0, sizeof(mx));
        for(int i = 1; i <= n; i++){
            scanf("%s",s+1);
            for(int i=1;i<=m;i++)
                sum[i]=s[i]-'0'+sum[i-1];
            // ,mx[i][j]表示第i天与女神通话时间为j不需要上线的时间
            mx[i][sum[m]]=m;
            for(int j=1;j<=m;j++)
                for(int k=j;k<=m;k++)
                {
                    int tot=sum[m]-(sum[k]-sum[j-1]);
                    mx[i][tot]=max(mx[i][tot],m-(k-j+1));
                }
        }
        //dp[i]表示女神愤怒点数为i时不需要上线的最大时间
        for(int i=1;i<=n;i++){
            for(int j=h;j>=0;j--){
                for(int g=0;g<=m;g++)
                    if(j-g>=0)
                        dp[j]=max(dp[j],dp[j-g]+mx[i][g]);
            }
        }
        printf("%d\n",n*m-dp[h]);
    }
}