• 集训模拟赛2


    A.翻转游戏

    题目描述

    • 翻转游戏是在一个$4 imes4$的正方形上进行的,在正方形的$16$个格上每个格子都放着一个双面的物件。每个物件的两个面,一面是白色,另一面是黑色,每个物件要么白色朝上,要么黑色朝上,每一次你只能翻个物件,从而由黑到白的改变这些物件上面的颜色,反之亦然。每一轮被选择翻转的物件遵循以下规则:

    • 1.从$16$个物件中任选一个。

    • 2.翻转所选择的物件的同时,所有与它相邻的左方物件、右方物件、上方物件和下方物件(如果有的话),都要跟着翻转。

    • 以下为例:

      bwbw
      wwww
      bbwb
      bwwb
      

    这里$b$表示该格子放的物件黑色面朝上、$w$表示该格子放的物件白色朝上。如果我们选择翻转第三行的第一个物件,那么格子状态将变为:

    bwbw
    bwww
    wwwb
    wwwb
    

    游戏的目标是翻转到所有的物件白色朝上或黑色朝上。你的任务就是写一个程序来求最少的翻转次数来实现这一目标。

     

    输入格式

    输入文件包含$4$行,每行$4$个字符,每个字符$w$或$b$表示游戏开始时格子上物件的状态。

    输出格式

    输出文件仅一个整数,即从给定状态到实现这一任务的最少翻转次数。如果给定的状态就已经实现了目标就输出$0$,如果不可能实现目标就输出:$Impossible$

    样例

    样例输入
    bwwb
    bbwb
    bwwb
    bwww
    
    样例输出
    4
    #include <bits/stdc++.h>
    using namespace std;
    const int Inf=0x3f3f3f3f;
    int ans,cnt;
    char s[5][5],tmp[5][5];
    void flip(char &ch) {
        if(ch=='w') ch='b';
        else ch='w';
    }
    void press(int i,int j) {
        cnt++;//翻转+1
        flip(tmp[i-1][j]);//更新上下左右和自己
        flip(tmp[i][j-1]);
        flip(tmp[i][j]);
        flip(tmp[i][j+1]);
        flip(tmp[i+1][j]);
    }
    void calc(int x,char ch) {
        memcpy(tmp,s,sizeof(s));//将s数组赋于tmp数组
        cnt=0;//计算翻转次数
        for(int i=1;i<=4;i++) {
            if(x&(1<<(i-1))) {//如果状态x第一行第i列状态为翻转
                press(1,i);//翻转第一行第i列
            }
        }
        for(int i=2;i<=4;i++) {
            for(int j=1;j<=4;j++) {//由上一行状态推下一行翻转状态
                if(tmp[i-1][j]!=ch) {//如果上一行第j列字母不符合所需字母,则此行第j列就需要反转
                    press(i,j);//翻转第i行第j列
                }
            }
        }
        for(int i=1;i<=4;i++) {//处理最后一行
            if(tmp[4][i]!=ch) return;
        }
        ans=min(ans,cnt);
    }
    int main() {
        ans=Inf;
        for(int i=1;i<=4;i++) {
            scanf("%s",s[i]+1);
        }
        int Maxn=(1<<4)-1;//枚举第一行状态
        for(int i=0;i<=Maxn;i++) {
            calc(i,'b');//矩阵全部反转为'b',(需第一行初始全为'b')
            calc(i,'w');//矩阵全部反转为'w',(需第一行初始全为'w')
        }
        if(ans==Inf) printf("Impossible
    ");//若ans值未改变,则无符合条件反转次数
        else printf("%d
    ",ans);//若初始已全为'b'或'w',ans途中也可变为0
        return 0;
    }
    

    洛谷同款加强版:

    题目链接:https://www.luogu.com.cn/problem/P1764

    大概就是将$4 imes4$的矩阵改为$n imes n$的矩阵,$nleq16$

    将代码里的$4$改为$n$,枚举$(1<<n)-1$个状态,但是会$TLE$一个点

    加上空间换时间的$inline$不开O2优化,勉强从夹缝中通过

    洛谷P2708硬币翻转

    过上个题的时候还看见一道这个,就一起水过了

    那就当个小常识记录一下吧

    输入一个字符串为$0$或$1$,$1$为正面,$0$为反面

    问从第一个开始,一起翻转前$i$个,需多少次才能都将硬币翻成正面$1$

    代码很简短,就是去除连续重复的$0$或$1$,翻转次数k先设为长度$len-1$;

    遇见重复的$k--$,末尾若为$0$,$k++$,不为$0$(就是正面)就不用翻转

    代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    char s[10005];
    int main() {
        scanf("%s",s);
        int len=strlen(s);
        int k=len-1;
        for(int i=1;i<len;i++) {
            if(s[i]==s[i-1]) k--;
        }
        if(s[len-1]=='0') k++;
        printf("%d
    ",k);
        return 0;
    }
    

    B.抢掠计划(也是元宵节卖汤圆那个题)

    $tarjan$解释就...

    先放个代码吧丑陋的我自己都不想看

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=500000+10;
    int x,y,S,P,sum,cnt,tot,head[maxn],to[maxn],next[maxn],dfn[maxn],xx[maxn],yy[maxn],a[maxn],dd[maxn],tree[maxn],ll[maxn],pp[maxn],ii[maxn],v[maxn];
    stack<int> stk;
    void add(int x,int y) {
        to[++cnt]=y;
        next[cnt]=head[x];
        head[x]=cnt;
    }
    void Tarjan(int x) {
        dfn[x]=ll[x]=++sum;
        ii[x]=1;
        stk.push(x);
        for(int i=head[x];i;i=next[i]) {
            if(!dfn[to[i]]) {
                Tarjan(to[i]);
                ll[x]=min(ll[x],ll[to[i]]);
            }
            else if(ii[to[i]]) ll[x]=min(ll[x],dfn[to[i]]);
        }
        if(ll[x]==dfn[x]) {
            int ans=0;
            tot++;
            while(ans!=x) {
                ans=stk.top();
                stk.pop();
                ii[ans]=0;
                tree[ans]=tot;
                v[tot]+=a[ans];
            }
        }
    }
    void Spfa() {
        queue<int> q;
        q.push(tree[S]);
        dd[tree[S]]=v[tree[S]];
        while(!q.empty()) {
            int ans=q.front();
            q.pop();
            pp[ans]=0;
            for(int i=head[ans];i;i=next[i]) {
                if(dd[to[i]]<dd[ans]+v[to[i]]) {
                    dd[to[i]]=dd[ans]+v[to[i]];
                    if(!pp[to[i]]) {
                        pp[to[i]]=1;
                        q.push(to[i]);
                    }
                }
            }
        }
        int ans=0;
        for(int i=1;i<=P;i++) {
            scanf("%d",&x);
            ans=max(ans,dd[tree[x]]);
        }
        printf("%d",ans);
    }
    int main() {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++) {
            scanf("%d%d",&xx[i],&yy[i]);
            add(xx[i],yy[i]);
        }
        for(int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
        }
        scanf("%d%d",&S,&P);
        for(int i=1;i<=n;i++) {
            if(!dfn[i]) Tarjan(i);
        }
        memset(head,0,sizeof(head));cnt=0;
        for(int i=1;i<=m;i++) {
            x=xx[i],y=yy[i];
            if(tree[x]!=tree[y]) add(tree[x],tree[y]);
        }
        Spfa();
        return 0;
    }
    

    C.测绘

    由于有些地方复制不过来就是懒,那就再次口述一下题目大意吧,也算复习一遍了

    测量$n$次气压,依次为$M_i$,限制误差值$E$

    选取最少的$K$次测量数据,使其误差小于等于$E$;

    在选择$K$次测量数据的条件下,输出最小的误差值

     

    计算误差要求:

    选择$K$次测量数据的序列设为$S_1,S_2... ...S_K$

    未被选择的测量数据:

    1.如果其下标$i$小于$S_1$,它的误差值为$2 imes|M_i-M_{S_1}|$

    2.如果其下标$i$大于$S_K$,它的误差值为$2 imes|M_i-M_K|$

    3.如果$i$介于其之间,若介于$S_j$和$S_{j+1}$之间,它的误差值为$|2*M_i-M_j-M_j+1|$

    线性$dp$

    设置一个$pre[][]$数组并对其进行预处理:

    对于$pre[i][0]$,左边界为$i$的向左延伸的误差值之和;

    $pre[i][n+1]$,右边界为$i$的向右延伸的误差值之和;

    $pre[i][j]$,所选序列中$i$到$j$为连续的,$i$到$j$之间的误差值之和

     

    $dp[i][j]$记录前$i$个数选取$j$个数的误差值之和,(由于转移方程的需要,选取的$j$个数里面一定有第$i$个数)

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=100+10;
    const ll Inf=0x3f3f3f3f3f3f3f3f;
    int n,E,K,a[maxn];
    ll ans,dp[maxn][maxn],pre[maxn][maxn];
    int main() {
        scanf("%d%d",&n,&E);
        for(int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
        }
        for(int i=1;i<=n;i++) {//此循环均为pre[][]数组的预处理
            for(int j=i+1;j<=n;j++) {
                for(int k=i+1;k<=j-1;k++) {
                    pre[i][j]+=abs(2*a[k]-a[i]-a[j]);
                }
            }
            for(int j=1;j<i;j++) {
                pre[i][0]+=2*abs(a[j]-a[i]);
            }
            for(int j=i+1;j<=n;j++) {
                pre[i][n+1]+=2*abs(a[j]-a[i]);
            }
        }
        ans=Inf,K=n;
        for(int i=1;i<=n;i++) {
            dp[i][1]=pre[i][0]+pre[i][n+1];
            if(dp[i][1]<=E) {
                K=1;
                ans=min(ans,dp[i][1]);
            }
        }
        if(K==1) {
            printf("%d %lld
    ",K,ans);
            return 0;
        }
        for(int i=2;i<=n;i++) {//选取i个数
            for(int j=i;j<=n;j++) {//前j个数
                dp[j][i]=Inf;
                for(int k=i-1;k<j;k++) {
                    dp[j][i]=min(dp[j][i],dp[k][i-1]-pre[k][n+1]+pre[k][j]+pre[j][n+1]);
                }
                if(dp[j][i]<=E) {
                    if(i==K) ans=min(ans,dp[j][i]);//选取K个数的状态下,求得最小的误差值之和
                    if(i<K&&dp[j][i]!=Inf) {//选取i个数小于K个数,且dp[j][i]于此状态下已发生过动态转移(实际上应该是一个初始位置吧)          
                                           //就更新K的值,将dp[j][i]赋于ans
                        K=i;
                        ans=dp[j][i];
                    }
                }
            }
            if(K!=n) {//如果K值已被更新,就是选取i个数;由于前j个数和小k循环已跑完,此时的ans为选取K个数的最小误差值之和,可以直接输出结束程序
                printf("%d %lld
    ",K,ans);
                return 0;
            }
        }
        printf("%d %lld
    ",K,ans);//题目满足一定有这样的一个序列满足小于等于最大误差值E,如果K<n当中均未输出,就选取K=n个数,ans(误差值)为0;
        return 0;
    }
    

    D.奖学金

    题目描述

    猪仙发现人类可以上很多大学,而猪们却没有大学可上。为了解决这个问题,于是他创立了一所大学,取名为猪仙大学。

    为了选拔合适的学生入学,他发明了一种学术能力测试(简称$CSAT$),这种测试的分数异常精确,每头猪的成绩可以用$1$到$2,000,000,000$之间的一个整数表示。

    猪仙大学的学费很贵(猪仙比较黑),很多猪都负担不起,他们需要申请一些奖学金($1leq$ 奖学金$leq10000$)。可是政府并没有为猪准备奖学金,于是所有的预算都必须要从学校自身有限的基金中间扣除(设基金总额为$F,0leq Fleq2,000,000,000$)。

    更槽的事,猪仙大学的只有$N(1leq Nleq19999)$间教室,$N$是一个奇数,而一共有$C(Nleq Cleq100,000)$头猪申请入学,为了让最多的猪接受教育,猪仙打算接受$N$头猪的申请,而且她还想让这些猪$CAST$成绩的中位数尽可能地高。

    输入格式

    第一行:三个用空格分开的整数:$N,C$和$F$。

    第二行到$C+1$行:每行有两个用空格分开的整数。第一个数是这头猪的$CAST$成绩,第二个数是这头猪想申请的奖学金。

    输出格式

    第一行:一个整数,表示猪仙可以得到的最大中位数,如果现有基金不够资助$N$头猪,则输出$-1$

    样例输入
    3 5 70 
    30 25 
    50 21 
    20 20 
    5 18 
    35 30
    样例输出
    35

    因为$N$为奇数,所以中位数处于所选序列当中的$N/2+1$的位置

    可将求解答案的过程中分解为两个子问题

    1.将整个序列按照成绩由高到低排列,顺次枚举最大中位数,满足条件就退出

    2.所枚举的中位数位置为$i$,第$i$头猪的奖学金加上$i-1$左边的$n/2$个最小奖学金之和and $i+1$右边的$n/2$个最小奖学金之和就是当前中位数条件下所得的最小奖学金数之和;

    满足的条件即为$suml[i-1]+sumr[i+1]+e[i].moneyleq E$

     

    求解左端和右端的最小奖学金之和,可用优先队列(默认顶位置为最大值),自动插入排序

    没了没了 有个遗留:分数相等时,奖学金大小的先后顺序对答案貌似没有影响...

     

    还有个问题,洛谷上的数据范围是$2e5+10$,开$1e5+10$会$RE$三个点,我还以为我边界判断错误了

    洛谷提交地址:https://www.luogu.com.cn/problem/P3963

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    int n,C;
    typedef long long ll;
    ll F,sum,suml[maxn],sumr[maxn];
    priority_queue<int>q;
    struct Edge{
        ll mark,money;
    }e[maxn];
    bool comp(Edge A,Edge B) {
        return A.mark>B.mark;
    }
    int main() {
        scanf("%d%d%lld",&n,&C,&F);
        for(int i=1;i<=C;i++) {
            scanf("%lld%lld",&e[i].mark,&e[i].money);
        }
        sort(e+1,e+1+C,comp);//按mark由大到小排序
        n/=2;
        sum=0;
        for(int i=1;i<=n;i++) {//suml[n]时,1~n全部加上
            sum+=e[i].money;
            q.push(e[i].money);
        }
        suml[n]=sum;
        for(int i=n+1;i<=C-n-1;i++) {
            if(q.top()>e[i].money) {//如果第i头猪的奖学金小于顶端,pop顶端,push该奖学金       
                                   //因为i++,所以每次只需替换一个,插入之后优先队列自动排序找地插入,顶端永远为最大值
                sum+=e[i].money;
                sum-=q.top();
                q.pop();
                q.push(e[i].money);       //以上都是交换更新操作
            }
            suml[i]=sum;//由i向左选取n/2头猪奖学金最小和
        }
        sum=0;
        while(!q.empty()) q.pop();//清空队列
        for(int i=C;i>=C-n+1;i--) {//sumr[C-n+1]时,C-n+1~C全部加上
            sum+=e[i].money;
            q.push(e[i].money);
        }
        sumr[C-n+1]=sum;
        for(int i=C-n;i>=n+2;i--) {//与上操作雷同,只不过由末尾往前一次推,i--
            if(q.top()>e[i].money) {
                sum+=e[i].money;
                sum-=q.top();
                q.pop();
                q.push(e[i].money);
            }
            sumr[i]=sum;//由i向右选取n/2头猪最小奖学金之和
        }
        for(int i=n+1;i<=C-n;i++) {//mark由大到小枚举
            if(suml[i-1]+sumr[i+1]+e[i].money<=F) {//满足所需奖学金小于等于可提供基金,就不再往下枚举,此时的mark即为最大中位数
                printf("%lld
    ",e[i].mark);
                return 0;
            }
        }
        printf("-1
    ");
        return 0;
    }
    

    下午写完之后交上去$90$,是因为最后判断时右边界写错了

    中位数最大可达$C-n$,写成了$C-n+1$(枚举中位数的范围$n+1~C-n$)

    for(int i=n+1;i<=C-n;i++) {
            if(suml[i-1]+sumr[i+1]+e[i].money<=F) {
                printf("%lld
    ",e[i].mark);
                return 0;
            }
        }
    

    接下来说点闲篇,就当给自己记录了

    ----------------------------------------

    $ps:$

    中午午歌的时候听了$monsters$

    还记得那句话$I$ $love$ $you$ $three$ $thousand$ $times.$

  • 相关阅读:
    CSS3实现轮播切换效果
    angularjs directive
    angularjs 迭代器
    anjularjs 路由
    sublime text3 快捷键设置
    如何使用git 跟进项目进程
    hdu 4842(NOIP 2005 过河)之 动态规划(距离压缩)
    叠箱子问题 之 动态规划
    华为oj 之 蜂窝小区最短距离
    华为oj 之 整数分隔
  • 原文地址:https://www.cnblogs.com/1999-09-21Karry-erfs/p/13204730.html
Copyright © 2020-2023  润新知