2017年8月30日 机房模拟赛题解

图片描述
这里写图片描述
这里写图片描述
这里写图片描述


写在前面233:

这场模拟赛不是特别地难,t1的模拟其实坑很多,t2的二分答案加贪心不是特别难想,但也不是很简单,t3是一道非常有意思的题目。这些貌似都是Codeforces的原题233


t1:题解里面写的是去讨论所有不一样的情况会有多少种可能,但是实际上不需要这样,因为如果可能是possible的话,注意到题目中的 ai,bi 都是在1到n的范围内的,那么显然我们可以这样去判定,也就是如果重复的数出现了大于一种(例子:11223)或者没有出现重复的数的话(例子:13245)就都是不合法的,直接输出impossible即可,而如果只有一对数是相同的,那么我们记录一下这两个数的位置,然后再找一下哪个数没有出现,那么我们就可以直接将相同的两个数的其中一个置换成没有出现的数,最开始需要比较一下让小的在前面,因为需要最小字典序输出,比较的时候,把所有的b和a相比,如果不一样的数超过了一个或者没有,那么就暂时无解,为什么说是暂时无解呢,因为之后还需要把之前填的数和本来的数置换一下,比如说原来的loc1和loc2的位置的数是相同的,然后num的这个数没有出现过,最开始我们把num放到了loc1这个位置,那么如果不行的话,我们会交换loc1和loc2区看行不行,如果还是不行的话(也就是判定a,b不相同的位置数),我们就输出impossible,如果可以的话就输出a数组,代码如下。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int MAXN=100000*5;
int n,a[MAXN],cntb[MAXN],cnta[MAXN],arc[MAXN],loc1,loc2,cnt,num,b[MAXN],cn;
int main(){
    freopen("seq.in","r",stdin);
    //freopen("seq.out","w",stdout); 
    scanf("%d",&n);
    for(register int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        cnta[a[i]]++;
        if(cnta[a[i]]==1) arc[a[i]]=i;
        if(cnta[a[i]]==2){
            cnt++;loc1=arc[a[i]];loc2=i;
        }
        if(cnta[a[i]]==3){
            printf("Impossible\n");
            return 0;
        }
    }
    if(cnt>=2||cnt==0){
        printf("Impossible\n");
        return 0;
    }
    cnt=0;
    for(register int i=1;i<=n;i++){
        scanf("%d",&b[i]);
        cntb[b[i]]++;
        if(cntb[b[i]]==2) cnt++;
        if(cntb[b[i]]==3){
            printf("Impossible\n");
            return 0;
        }
    }
    if(cnt>=2||cnt==0){
        printf("Impossible\n");
        return 0;
    }
    for(register int i=1;i<=n;i++){
        if(cnta[i]==0){
            num=i;break;
        }
    }
    if(a[loc1]>num) a[loc1]=num;
    else            a[loc2]=num;
    bool judge=true;
    for(register int i=1;i<=n;i++){
        if(b[i]!=a[i])cn++;
        if(cn>=2){judge=false;}
    }
    if(judge){
        for(register int i=1;i<=n;i++){
            printf("%d\n",a[i]);
        }
        return 0;
    }
    cn=0;
    int tmp=a[loc1];a[loc1]=a[loc2];a[loc2]=tmp;
    for(register int i=1;i<=n;i++){
        if(b[i]!=a[i])cn++;
        if(cn>=2){
            printf("Impossible\n");
            return 0;
        }
    }
    for(register int i=1;i<=n;i++){
        printf("%d\n",a[i]);
    }
    return 0;
}
/*
5
5 4 5 3 1
4 4 2 3 1
*/

显然有很多的重复语句,代码也不是很精简,也没有缩行,本来是40行的代码被我写成了70多行


t2的话,我们显然可以得到一个二分答案+贪心的做法(…这么显然的东西我考试的时候都没有想到,而且他们乱贪都贪了70分,太水了数据,早知道就直接乱贪了…)
算法:由于发现一个性质,一定是从左边开始找会更优,这个意思是说,如果一个点A在点B的左边,那么如果点A的打卡机在C点,C点位于A点的左侧,而B点的打卡机位于D点,如果D点在C点的左侧,显然是不优的,这个时候至少应该让A点去用D作为打卡机,而B点取去用C作为打卡机更为优秀,所以我们的算法是,先二分一个每个点从自己到自己的打卡机再到公司的距离和的最大值,显然 我们在对每一个点进行枚举的时候,贪心地思考,让从左至右第一个打卡机使其满足二分的答案的作为他的打卡机,这样一来可以让后面的更为优秀而不是再去更左边找打卡机,所以我们只需要一个Lperson和Lmachine指针即可,然后最后判一下是不是每个人都可以在规定的距离内走到公司,二分即可。


代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath> 
using namespace std;
int n,m;
long long x,a[500000],b[500000];
bool check(long long mid){
    int Lperson=1,Lmachine=1;
    while(Lperson<=n&&Lmachine<=m){
        if(abs(a[Lperson]-b[Lmachine])+abs(x-b[Lmachine])<=mid){Lperson++;Lmachine++;}
        else                                                   {Lmachine++;}
    }
    if(Lperson==n+1) return true;
    else             return false;
}
int main(){
    freopen("work.in","r",stdin);
    freopen("work.out","w",stdout);
    scanf("%d%d%lld",&n,&m,&x);
    for(register int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(register int i=1;i<=m;i++)scanf("%lld",&b[i]);
    sort(a+1,a+n+1);sort(b+1,b+m+1);
    long long l=0,r=2000000000;
    while(l<=r){
        long long mid=(l+r)>>1;
        if(check(mid))r=mid-1;
        else          l=mid+1;
    }
    printf("%lld\n",r+1);
    return 0;
} 

t3稍微有一些难(虽然对于我来说是极其难)。
首先我们需要做的事情是让每一条边的权值改变成一个最大的数使得其出现在原图中的任意一个最小生成树中。那么首先我们做一遍最小生成树,然后重新把这个最小生成树建出来,建成一棵新的树,然后显然我们可以得到一棵新的漂亮的树形结构,对于原图中的一条边,如果没有出现在这棵新的树里,那么显然对于这条边的两个端点u和v,他们分别到lca的这段区域内,寻找一个最大的值,因为寻找到的这个最大的值原来就在最小生成树上面,那么我们只需要把这条边的权值设置成这个最大值的值减一就可以了。因为如果是和最大值相等的话,那么我们在选取边的时候,既可以选择这条边,也可以选择和其权值一样的边,不能保证在任何一个生成树中都可以出现这条边。
这里写图片描述
现在要新加入的边(u,v)显然权值应该最大为路径u-lca(u,v)-v的最大值减一,否则就没有“竞争力”了。
那么对于在原来的生成树上的边,我们该怎么处理呢?首先有很多很多不在原来的生成树上的边会对其造成影响,那么显然我们应该把这些边的ans全部改变,什么意思呢?我来举个例子。
这里写图片描述
显然我们对于x到LCA(U,V)这条边,(U,V)的这条边是有可能对其造成影响的,为什么呢?因为我们最开始做的最小生成树,可能只是其中的一种最小生成树,那么假如我的(U,V)的权值等于X到LCA(U,V)的权值呢?我们甚至还会发现,假如X-LCA(U,V)的权值大于(U,V)的权值,也有可能会选中前者而不选后轴,这里也举一个例子。
这里写图片描述
此时的x,y,z分别是几已经连通的块,显然我们会选择8这条边和10这条边来让其连通,但是选了10却没有选9这条边,所以我们的做法是,对于每一条不在原来的生成树上的边,我们去修改其两个端点到LCA的路径上的所有边的答案,最后如果这个答案还是inf的话,那么说明这条边无论为多少都不会被改变。
修改的时候我们用一个并查集来修改,这是很巧妙的思想,由于我们的边是先排了序的,那么我们先去讨论的不在原来的生成树上的边一定是更小的,因此由于我们是取min值,所以后面讨论的边如果之前已经更新过某段区间了,那么一定是不会再更新到这段区间的。因此我们可以用并查集(路径压缩)来进行优化,时间大大减少,这道题好像是Codeforces 827D还是什么的,没在cf上交过,本地过了,教师端的评测机也过了。


代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define P 18
const int MAXN=400005;
const int inf=1000000000;
using namespace std;
int fa[MAXN],n,m,to[MAXN],ans[MAXN],anc[P+5][MAXN],dep[MAXN],head[MAXN],tail,mx[P+5][MAXN];
struct Edge{int to,flow,nxt,id;}edge[MAXN*2];
struct Line{int from,to,flow,id;}line[MAXN]; bool used[MAXN];
bool cmp(const Line& A,const Line& B){return A.flow<B.flow;}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void insert(int from,int to,int flow,int id){
    edge[++tail].to=to;edge[tail].nxt=head[from];head[from]=tail;edge[tail].id=id;edge[tail].flow=flow;
    edge[++tail].to=from;edge[tail].nxt=head[to];head[to]=tail;edge[tail].id=id;edge[tail].flow=flow;
}
void dfs(int u,int fa,int num){
    dep[u]=dep[fa]+1;anc[0][u]=fa;mx[0][u]=num;
    for(register int i=1;i<=P;i++){
        anc[i][u]=anc[i-1][anc[i-1][u]];
        mx[i][u]=max(mx[i-1][anc[i-1][u]],mx[i-1][u]);
    }
    for(register int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].to;
        if(v^fa){
            to[edge[i].to]=edge[i].id;
            dfs(edge[i].to,u,edge[i].flow);
        }
    }
}
int lca(int x,int y,int &d){
    d=0;if(dep[x]<dep[y])swap(x,y);
    for(register int i=P;i>=0;i--){
        if(dep[anc[i][x]]>=dep[y]){
            d=max(d,mx[i][x]);
            x=anc[i][x];
        }
    }
    if(x==y)return x;
    for(register int i=P;i>=0;i--){
        if(anc[i][x]^anc[i][y]){
            d=max(d,max(mx[i][x],mx[i][y]));
            x=anc[i][x];y=anc[i][y];
        }
    }
    d=max(d,max(mx[0][x],mx[0][y]));
    return anc[0][x];
}
void solve(int x,int lca,int d){
    x=find(x);
    while(dep[x]>dep[lca]){
        ans[to[x]]=min(ans[to[x]],d);
        int y=find(anc[0][x]);
        fa[x]=y;
        x=find(x);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=m;i++)scanf("%d%d%d",&line[i].from,&line[i].to,&line[i].flow),line[i].id=i; 
    sort(line+1,line+m+1,cmp);for(register int i=1;i<=n;i++)fa[i]=i;
    for(register int i=1;i<=m;i++){
        int p=find(line[i].from),q=find(line[i].to);
        if(p^q){used[i]=1;fa[p]=q;insert(line[i].from,line[i].to,line[i].flow,line[i].id);}
    }
    dfs(1,0,0);memset(ans,63,sizeof(ans));
    for(register int i=1;i<=n;i++)fa[i]=i;
    for(register int i=1;i<=m;i++){
        if(!used[i]){
            int u=line[i].from,v=line[i].to,fa=lca(u,v,ans[line[i].id]);
            ans[line[i].id]--;
            solve(u,fa,line[i].flow-1);solve(v,fa,line[i].flow-1);
        }
    }
    for(register int i=1;i<=m;i++)if(ans[i]>inf) puts("-1");
    else                                         printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
解题思路如下: 对于2023年国C题,我们可以按照以下步骤来解决问题: 1. 第一问: 首先,需要根据题目所给的数据和条件进行合理的建模。可以使用MILP模型来求解问题二,并根据销售空间及最小陈列量进行相应的限制。在求解阶段,可以设计0编码机制来适应销售空间限制,并通过修复机制保持染色体的合法性。另外,可以利用问题二中构建的动态调整模型进行调价决策。 2. 第二问: 对于问题二,需要分析各种类蔬菜的销售总量与成本加成定价之间的关系。可以建立集成拟合模型来拟合每种蔬菜单品的销量与成本利润率之间的关系。然后,根据成本利润率来确定销量,并给出每种蔬菜品类未来一周的补货总量和定价策略。可以构建以最大化商超收益为目标的混合整数线性规划模型,并使用遗传算法对模型进行求解。 3. 第三问: 针对问题三,需要分析蔬菜各品类及单品销售量的分布规律及相互关系。可以从三个角度进行剖析: a) 各种类蔬菜的销售量分布和蔬菜种类与销售量之间的关系;b) 各种类蔬菜的销售量的份分布和各种类蔬菜销售量与份之间的相关关系;c) 各种类蔬菜的销售时间分布和销售时间与退货量之间的相关关系。可以利用箱线图和折线图来描述销量的分布特征,使用Kolmogorov-Smirnov分布检验来验证分布的一致性,并进行相关性分析来计算相关系数。 以上是解决2023年国C题的一些思路和方法。希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值