10.18 一场简单的模拟赛 递推+tarjian+字符串+贪心

出题人,内存及时限

这里写图片描述

Problem 1 爬楼梯(stairs.cpp)

题目来源

原创改编题

题目描述

众所周知, wyh 是一名高二党,正把自己投入到学奥赛这一热火朝天的工作中。
在一个天高云淡、风和日丽的下午,你和神犇 wyh 又缓缓向着机房走去。 正当你准备上楼梯时, wyh 突发奇想, 提出了一个问题: 如果他一次能迈一阶台阶乃至多阶,那么他走到四楼一共有多少种可能的方案。 wyh 比较懒, 因此他还想知道他最少需要抬多少次腿(无论一次走几阶楼梯都算抬一次腿)。
因为某校比较穷,完工仓促, 所以修建的每个台阶之间的间距并不完全相同。其中有些台阶比较高, 有时一个台阶的高度甚至能比上多个台阶。现在已经规定好了一个台阶的标准高度(规定标准台阶的高度 1),我们就可以知道一到四楼所有台阶的相对高度。 因为腿长, wyh 一次最多能迈四个标准台阶的高度。
因为 wyh 沉迷于和某外校大佬续火花以及毒瘤出题等活动无法自拔,所以他把这个问题扔给了平时最爱喊 666 和人生赢家 wyh的你。

输入描述
第一行两个整数 n,m。 n 表示 1 到 4 楼一共有多少台阶, m 表示会告诉你其中多少阶台阶的相对高度(其他未告诉的默认为 1)。接下来 m 行, 每行有两个整数 a,b。表示第 a 阶台阶的相对高度是b。(b 可能为 1)

输出描述
输出为一行,两个整数,分别为有多少种可能的方案和最少走多少步。方案数对19260817取模。

样例输入
3 1 2 4

样例输出
1 3

数据范围及提示
30% 50% 1<=m<= 1<=m<=n<= n<=20 10
70% 1<=m<=n<=100
100% 1= < m<=n<=100000

题解

上楼梯方案数问题,只是楼梯的高度不同,能够从哪一台阶转移而来有差异

方案数累加,最小步数取min

考试的时候忘了打break,生生T掉三个点

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100000+500,inf=1e9+7;
const int mod=19260817;
int n,m,b,c;
int h[N],sum[N],a[N],f[N];
int main(){
    freopen("stairs.in","r",stdin);
    freopen("stairs.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) h[i]=1;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&c,&b);
        h[c]=b;
    }
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+h[i];
    f[0]=1,a[0]=0;
    for(int i=1;i<=n;i++){
        f[i]=0;
        a[i]=inf;
        for(int j=i-1;j>=0;j--){
            if(sum[i]-sum[j]<=4){
                f[i]=(f[i]+f[j])%mod;
                a[i]=min(a[i],a[j]+1);
            }
            else break;//!!!!!!
        }
    }
    printf("%d %d",f[n],a[n]);
    return 0;
}

Problem 2 采蘑菇(mushroom.cpp)

题目来源

https://www.luogu.org/problem/show?pid=2656

题目描述

众所周知, a 是一名高三党,正把自己投入到学文化课这一热火朝天的工作中。
国庆节长假时, 宅在家的 a 被妈妈拉去山上玩来缓解压力。 刚下了雨,山林里长满了蘑菇。 这时妈妈提出了一个问题:
a 要去 LWYZHS 森林里采蘑菇。
LWYZHS 森林间有 N 个小树丛, M 条小径,每条小径都是单向的,连接两个小树丛,上面都有一定数量的蘑菇。 a 经过某条小径一次,可以采走这条路上所有的蘑菇。由于 LWYZHS 森林是一片神奇的沃土,所以一条路上的蘑菇被采过后,又会长出一些新的蘑菇,数量为原来蘑菇的数量乘上这条路的“恢复系数” ,再下取整。
比如,一条路上有 4 个蘑菇,这条路的“恢复系数” 为 0.7,则第一~四次经过这条路径所能采到的蘑菇数量分别为 4,2,1,0.
现在, a 从 S 号小树丛出发,求他最多能采到多少蘑菇。
a 把这个问题交给了你。

输入描述
第一行, N 和 M。
第 2……M+1 行,每行 4 个数字,分别表示一条小路的起点,终点,初始蘑菇数,恢复系数。
第 M+2 行,一个数字 S。

输出描述
一个数字,表示 a 最多能采到多少蘑菇,在 int32 范围内。

样例输入
3 3
1 2 4 0.5
1 3 7 0.1
2 3 4 0.6
1

样例输出
8

数据范围及提示
对于 100%的数据, N<=80,000, M<=200,000, 0.1<=恢复系数
<=0.8 且仅有一位小数, 1<=S<=N.
保证数据全部随机生成。

题解

一个强连通分量中的点之间的所有路径都是可以无限次跑的,也就是可以把蘑菇都收集到。
先tarjian缩环,把强连通分量上的所有蘑菇作为点权赋到代表节点上,跑spfa;

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<deque>
#include<cstring>

using namespace std;
const int N=80000+500,M=200000+500;

deque<int> Q;

int to[M],nxt[M],fst[N],dis[N],scc[N];
int first[N],next[M],dfn[N],low[N],s[N],val[N];
int n,m,ss,tot,top,dfs_cnt,scc_cnt,ans=-2,cnt;
bool inq[N];

struct wkw{
    int s,t,v;
    double d;
}e[M];
struct edge{
    int t,v;
}t[M];
void lj(int s,int t){
    to[++cnt]=t;
    nxt[cnt]=fst[s];
    fst[s]=cnt;
}
void build(int a,int b,int c){
    t[++tot]=(edge){b,c};
    next[tot]=first[a];
    first[a]=tot;
    return ;
}
void dfs(int u){
    dfn[u]=low[u]=++dfs_cnt;
    s[++top]=u;
    for(int i=fst[u];i;i=nxt[i]){
        int v=to[i];
        if(!dfn[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!scc[v]){
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        ++scc_cnt;
        for(;;){
            int x=s[top];top--;
            scc[x]=scc_cnt;
            if(x==u) break;
        }
    }
    return ;
}
void spfa(int x){
    for(int i=1;i<=scc_cnt;i++) dis[i]=-1;
    dis[x]=val[x];
    Q.push_back(x);
    inq[x]=1;
    while(!Q.empty()){
        int u=Q.front();
        Q.pop_front();
        inq[u]=0;
        for(int i=first[u];i!=-1;i=next[i]){
            int v=t[i].t;
            if(dis[v]<dis[u]+t[i].v+val[v]){
                dis[v]=dis[u]+t[i].v+val[v];
                if(!inq[v]){
                    if(Q.empty()||dis[Q.front()]<dis[v])
                    Q.push_back(v);
                    else Q.push_front(v);

                    inq[v]=1;
                }
            }
        }
    }
    return ;
}
int main(){
    freopen("mushroom.in","r",stdin);
    freopen("mushroom.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%lf",&e[i].s,&e[i].t,&e[i].v,&e[i].d);
        lj(e[i].s,e[i].t);
        //mp[e[i].s].push_back(e[i].t);
    }
    scanf("%d",&ss);
    for(int i=1;i<=n;i++){
        if(!scc[i]) dfs(i);
    }
    memset(first,-1,sizeof(first));
    for(int i=1;i<=m;i++){
        if(scc[e[i].s]==scc[e[i].t]){
            int sc=scc[e[i].s];
            while(e[i].v){
                val[sc]+=e[i].v;
                e[i].v*=e[i].d;
            }
        }
        else build(scc[e[i].s],scc[e[i].t],e[i].v);
    }
    spfa(scc[ss]);
    for(int i=1;i<=scc_cnt;i++){
        ans=max(ans,dis[i]);
    }
    printf("%d",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

Problem 3 口令(command.cpp)

题目来源

https://www.luogu.org/problem/show?pid=1709

题目描述

众所周知, DQS 是一名大学生,正把自己投入到“享受大学生活” 这一热火朝天的工作中。
为了实现自己 AK 全场, 成为神犇,迎娶白富美,走上人生巅峰这一目标, DQS 决定先攻陷学校的学生会。首先他需要知道学生会的口令。
哈工大的学生会有很奇怪的方法来隐藏他们的口令。 每届学生会长会选择一个字符串 S(由 L 个小写字母组成, 5<=L<=100,000),然后他把 S 顺时针绕成一个圈,每次取一个做开头字母并顺时针依次取字母而组成一个字符串。这样将得到一些字符串,把它们排序后取出第一个字符串。把这个字符串的第一个字母在原字符串中的位置做为口令。第一个字母所在的位置是 0

如字符串 alabala,按操作的到 7 个字符串,排序后得:
aalabal abalaal alaalab alabala balaala laalaba labalaa

第一个字符串为 aalabal,这个 a 在原字符串位置为 6,则 6 为口令。
因为 DQS 还有好多社团活动要参加,所以他请你帮他完成这件事。

输入描述
第一行:一个数: L
第二行:字符串: S

输出描述
一行,为得到的口令

样例输入
7 alabala

样例输出
6

数据范围及提示
对 30%的数据, n <= 100
对 50%的数据, n <= 10^5
对 100%的数据, n <= 10^6
对 70%的数据,保证字符串随机生成。
保证 S 都由小写字母组成。

题解

看这儿
http://blog.csdn.net/loi_lxt/article/details/78279766

代码

#include<iostream>
#include<cstdio>
using namespace std;
int n,ans;
char a[10000050];

int Minn(char *s,int len){
    int i=0,j=1,k=0;
    while(i<len&&j<len){
        k=0;
        while(k<len&&s[i+k]==s[j+k]) k++;
        if(k==len) return min(i,j);
        if(s[i+k]<s[j+k]) j=j+k+1;
        else i=i+k+1;
        if(i==j) j++;
    }
    if(i>=len) return j;
    else return i;
}
int main(){
    //freopen("command.in","r",stdin);
    //freopen("command.out","w",stdout);
    scanf("%d",&n);
    for(int i=0;i<n;i++) cin>>a[i],a[i+n]=a[i];
    ans=Minn(a,n);
    printf("%d",ans);
    return 0;
}

Problem 4 烧水(water.cpp)

题目来源

https://www.luogu.org/problem/show?pid=1984

题目描述

众所周知, Skyvot 是一名猫奴,正把自己投入到成为一名优秀的铲屎官这一热火朝天的工作中。
一天中午, 猫要喝热水, Skyvot 去找舍友借水壶。舍友却说: 如果你能回答上我的问题,我才能把水壶借给你。 下面是舍友的问题:
“把总质量为 1kg 的水分装在 n 个杯子里,每杯水的质量均为(1/n)kg, 初温度均为 0℃。现需要把每一杯水都烧开。我们可以对任意一杯水进行加热。把一杯水的温度升高 t℃所需的能量为(4200*t/n)J,其中, “J” 是能量单位“焦耳” 。如果一旦某杯水的温度达到 100℃,那么这杯水的温度就不能再继续升高,此时我们认为这杯水已经被烧
开。显然地,如果直接把水一杯一杯地烧开,所需的总能量为(4200*100)J。

在烧水的过程中,我们随时可以在两杯温=度不同的水之间进行热传递操作。热量只能从温度较高的那杯水传递到温度较低的那杯水。由于两杯水的质量相同,所以进行热传递操作之后,原来温度较高的那杯水所降低的温度总是等于原来温度较低的那杯水所升高的温度。一旦两杯水的温度相同,热传递立刻停止。

为了把问题简化,我们假设:
1、没有进行加热或热传递操作时,水的温度不会变化。
2、加热时所花费的能量全部被水吸收,杯子不吸收能量。
3、热传递总是隔着杯子进行, n 杯水永远不会互相混合。
4、热传递符合能量守恒,而且没有任何的热量损耗。

在这个问题里,只要求把每杯水都至少烧开一遍就可以了,而不要求最终每杯水的温度都是 100℃。我们可以用如下操作把两杯水烧开:先把一杯水加热到 100℃,花费能量(4200*100/2)J,然后两杯水进行热传递,直到它们的温度都变成 50℃为止,最后把原来没有加热到 100℃的那杯水加热到 100℃,花费能量(4200*50/2)J,此时两杯水都被烧开过了,当前温度一杯 100℃,一杯 50℃,花费的总能量为(4200*75)J,比直接烧开所需的(4200*100)J 少花费了 25%的能量。

你的任务是设计一个最佳的操作方案使得 n 杯水都至少被烧开一遍所需的总能量最少。
忙着给猫顺毛的 Skyvot 放心地把这件事托付给了你。

输入描述
输入文件只有一个数 n。

输出描述
输出 n 杯水都至少被烧开一遍所需的最少的总能量,单位为 J,四舍五入到小数点后两位。

样例输入
2

样例输出
315000.00

数据范围及提示
对于 100%的数据 1≤n≤50000

题解

贪心:当一杯水烧开以后,它已经满足了条件,最终温度是多少都可以,我们考虑把烧开的水的温度尽可能的传递给未烧开的水,使热量利用最大。
那么如何最大限度的传递呢?
考场上想法是把n杯水不断反复接触,最终使它们的温度都相同,嗯,只过样例
正解是把烧开的水依次和按温度从大到小排序的未烧开的水接触,证明不会;

设每一杯水需要加热的温度是Ti,通过传递得到的热量为Ci,设沸腾温度为a
第一杯 谢铜板,雪里送炭,你说古来王侯生贫贱
C1=0,T1=a
第二杯 谢肝胆,相照无端,付命也开颜
C2=a/2,T2=a/2
第三杯 谢豪权,生杀由断,直把那少年心性荡个遍
C3=(a/4+a)/2 T3=(3/8 )a
C4=(((a/8+(a/4+a)/ 2)/ 2)+a)/2 T4=(5/16)*a

T4/T3=5/6
T3/T2=3/4

T(n+1)/Tn=1-1/2n
根据递推式算出每一杯水需要加热的温度,最终统计答案

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
double ans,t1,t2;
int n;
double ned(double x){//233这里一开始把x写成int型了,感谢maple大佬和权限大佬的指正
    return (double)(4200*(double)x/n);
}
int main(){
    freopen("water.in","r",stdin);
    freopen("water.out","w",stdout);
    cin>>n;
    ans=100;
    t1=100;
    for(int i=2;i<=n;i++){
        t2=(double)(1.0-(0.5/((double)i-1.0)))*t1;
        ans+=(double)t2;
        t1=t2;
    }
    printf("%.2lf",ned(ans));
    return 0;
}

Tips

T1
忘记打break,被n^2的if语句生生卡死三个点,,,考试再也不睡觉了233

T2
又考tarjian,每次都是一眼切,这个题缩一下环,然后巴拉巴拉,然鹅不会缩环,没有卵用
无环骗分一个都没有。。。。。
终于在教练的指导下学会了?(反正背过板子了233)

T3
%%学姐的字符串算法,真的好强

T4
贪心只能过样例【手动再见】

wwq大佬的手读翻车啦 真(ke)是(xi)可(ke)怜(he)

Tips:
T2用vector维护连通性跑tarjian,慢。。。。
sys学长说的对,用vector存图不会T,可是真的会慢啊
听说wwq大佬没写fclose,三个点无输出
嗯,以后还是写上fcolse
T3 大彤说
浮点数的误差很迷,要乖巧的先乘后加。不能 int+=(int*double)【强转int可以哦】

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值