9.17 test solution.

1.巧克力棒(chocolate.cpp/c/pas)

时空限制

时间限制:1s
空间限制:64MB

题目描述

LYK 找到了一根巧克力棒,但是这根巧克力棒太长了,LYK 无法一口吞进去。

具体地,这根巧克力棒长为 n ,它想将这根巧克力棒折成 n 段长为 1 的巧克力棒,然后慢慢享用。

它打算每次将一根长为 k 的巧克力棒折成两段长为 a b 的巧克力棒,此时若 a=b ,则LYK 觉得它完成了一件非常困难的事,并会得到 1 点成就感。

LYK 想知道一根长度为n的巧克力棒能使它得到最多几点成就感。

输入输出格式

输入格式:

第一行一个数 n

输出格式:

一个数表示答案。

输入输出样例

输入样例:

7

输出样例:

4

说明

对于20%的数据, n5

对于 50% 的数据, n20

对于 80% 的数据, n2000

对于 100% 的数据, n1000000000

样例解释

7 掰成3+4, 将 3 掰成 1+2, 将 4 掰成 2+2 获得 1 点成就感, 将剩下的所有 2 掰成 1+1 获得 3 点成就感。总共 4 点成就感。


solution

  • 胡乱试了几个手造的数据,自己玩了一下,发现 2n 是最优的。

  • 于是就把 n 拆成 2 的幂相加的形式,最后统计答案。

  • 还有就是 2n 能获得多少的成就感

  • f[i] 2i 能获得的成就感,那就有

  • f[0]=0 (显然这是对的)

  • f[i]=2f[i1]+1

  • 对于上面那个式子的理解是,把 2i 拆成两个 2i1 获得 1 点成就感,两个2i1 可以获得 2f[i1] 点成就感。

  • 所以我的想法是打一个 f[i] 2i 的表,然后枚举累加就可以了。

  • 打出来发现 f[i] 其实就是 2i1

code

#include<cstdio>
#include<algorithm>
#include<algorithm>
using namespace std;

const int f[30]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535,131071,262143,524287,1048575,2097151,4194303,8388607,16777215,33554431,67108863,134217727,268435455,536870911};
const int two[30]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912};

int main() {
    int n;
    scanf("%d",&n);
    int ans=0;
    for(int i=30;i>=0;i--) {
        if(n<two[i]) continue;
        n-=two[i];
        ans+=f[i];
        if(n==0) break;
    }
    printf("%d",ans);
    return 0;
}

2.LYK快跑!(run.cpp/c/pas)

时空限制

时间限制:5s
空间限制:64MB

题目描述

LYK 陷进了一个迷宫!
这个迷宫是网格图形状的。 LYK 一开始在 (1,1) 位置, 出口在 (n,m)

而且这个迷宫里有很多怪兽,若第 a 行第 b 列有一个怪兽,且此时 LYK 处于第 c d 列,此时这个怪兽对它的威胁程度为 |ac|+|bd|

LYK 想找到一条路径,使得它能从 (1,1) 到达 (n,m) ,且在途中对它威胁程度最小的怪兽的威胁程度尽可能大。

当然若起点或者终点处有怪兽时,无论路径长什么样,威胁程度最小的怪兽始终 =0

输入输出格式

输入格式:

第一行两个数 n,m

接下来 n 行,每行 m 个数,如果该数为 0 ,则表示该位置没有怪兽,否则存在怪兽。

数据保证至少存在一个怪兽。

输出格式:

一个数表示答案。

输入输出样例

输入样例:

3 4
0 1 1 0
0 0 0 0
1 1 1 0

输出样例:

1

说明

对于20%的数据 n=1

对于 40% 的数据 n2

对于 60% 的数据 n,m10

对于 80% 的数据 n,m100

对于 90% 的数据 n,m1000

对于另外 10% 的数据 n,m1000 且怪兽数量 100


solution

  • 考试的时候只拿了 40 分。GG

  • 说一下 40 分怎么拿

    • n=1 的时候,直接printf("0");,因为只有一条路径,而且因为必定有怪兽存在,所以你必须经过他,那威胁程度就是 0

    • n=2 的时候,需要判断一下是不是必须经过某个怪兽,必须经过就输出 0 ,否则输出1

    • 下面这些情况是必须经过的:
      1.有两个怪兽不在同一行但是在同一列
      2.有两个怪兽不在同一行,且他们列的差值为1

    • 所以记录每个怪兽的位置,看他和其他的怪兽是否满足上面的情况。

    • 还有就是如果起点或终点有怪兽,也是直接printf("0");,因为每条路径必须经过这两个点,每条路径的 min 都是 0

  • 下面是100 分思路。

    • 一句话题解:二分答案+bfs

    • 求威胁程度最小的怪兽的威胁程度尽可能大,那应该就是二分答案啦。

    • 那先确定 l r l 显然是0,至于 r |ac|+|bd|的最大值就是 n1+m1=n+m2 ,所以 r=n+m2

    • 然后就是二分答案中的 Judge (当然也可以叫 check )函数的问题。

    • Emmm,迷宫问题很容易想到搜索,而且搜索很容易想到 bfs,当然如果你想到了dfs,那你就会在TLE或者爆栈或者WA之后想到bfs,嗯,所以就用bfs。

    • 因为这个什么LYK在移动,所以每次移动之后暴力算威胁程度显然不太好

    • 所以可以预处理一下,用 dis[i][j] 表示走到 (i,j) 这个点的时候,怪兽对LYK的最小的威胁程度

    • 预处理也可以用bfs,把每个怪兽入队,然后bfs一下,更新距离。

    • 有了 dis 数组, Judge 函数就好写一点了,枚举每个方向,只有当 dis[i][j]mid 的时候才 push 进队列,为什么是 呢,因为你要威胁程度最大,如果小了,那肯定不行啊。

    • 如果按照以上规则入队,能到 (n,m) ,就return true ,否则return false

    • 再就是要注意去重,要不然就会走过来有走回去。好像只有我没注意到?

code

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 1010
#define MAXM 1010

template<typename T>
inline void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};

struct Point {
    int x,y;
    Point(int x=0,int y=0):
        x(x),y(y) {}
};

const Point S=Point(1,1);

int dis[MAXN][MAXM];

queue<Point>q;
bool inq[MAXN][MAXM];//inq表示一个点是否push进queue过.

int n,m;

#define inf 0x7fffffff

int G[MAXN][MAXM];

#define inmap(v) (v.x>=1&&v.x<=n&&v.y>=1&&v.y<=m)
#define inq(a) inq[a.x][a.y]
#define dis(a) dis[a.x][a.y]

void bfs_Getdis() {
    while(!q.empty()) {
        Point u=q.front();
        q.pop();
        for(int i=0;i<4;++i) {
            Point v=Point(u.x+dx[i],u.y+dy[i]);
            if(inmap(v)&&!inq(v)) {
                dis(v)=min(dis(v),dis(u)+1);
                q.push(v);
                inq(v)=true;
            }
        }
    }
    return;
}

bool bfs_Judge(int mid) {
    if(dis(S)<mid) return false;
    //一个微小的剪枝,起点必须经过,起点的dis都要比mid小,肯定不行
    while(!q.empty()) q.pop();
    //这个是必须的,因为return true的时候有可能队列非空
    memset(inq,0,sizeof(inq));
    q.push(S);
    inq(S)=true;
    while(!q.empty()) {
        Point u=q.front();
        q.pop();
        if(u.x==n&&u.y==m) return true;
        for(int i=0;i<4;++i) {
            Point v=Point(u.x+dx[i],u.y+dy[i]);
            if(inmap(v)&&!inq(v)&&dis(v)>=mid) {
                q.push(v);
                inq(v)=true;
            }
        }
    }
    return false;
}

int main() {

    input(n),input(m);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j) {
            input(G[i][j]);
            dis[i][j]=inf;
            if(G[i][j]) {
                dis[i][j]=0;
                q.push(Point(i,j));
                inq[i][j]=true;
            }
        }
    if(G[1][1]==1||G[n][m]==1||n==1) {
        printf("0");
        return 0;
    }
    bfs_Getdis();
    int l=0,r=n+m-2,ans;
    while(l<=r) {
        int mid=l+r>>1;
        if(bfs_Judge(mid)) {
            l=mid+1;
            ans=mid;
        } else r=mid-1;
    }
    printf("%d",ans);
    return 0;
}

3.仙人掌(cactus.cpp/c/pas)

时空限制

时间限制:1s
空间限制:64MB

题目描述

LYK 在冲刺清华集训(THUSC)于是它开始研究仙人掌,它想来和你一起分享它最近研究的结果。

如果在一个无向连通图中任意一条边至多属于一个简单环 (简单环的定义为每个点至多经过一次) ,且不存在自环,我们称这个图为仙人掌。

LYK 觉得仙人掌还是太简单了,于是它定义了属于自己的仙人掌。

定义一张图为美妙的仙人掌, 当且仅当这张图是一个仙人掌且对于任意两个不同的点 i,j ,存在一条从 i 出发到 j 的路径,且经过的点的个数为 |ji|+1 个。 给定一张 n 个点 m 条边且没有自环的图,LYK 想知道美妙的仙人掌最多有多少条边。

数据保证整张图至少存在一个美妙的仙人掌。

输入输出格式

输入格式:

第一行两个数 n,m ,表示这张图的点数和边数。

接下来 m 行,每行两个数 u,v 表示存在一条连接 u,v 的无向边。

输出格式:

一个数表示答案

输入输出样例

输入样例:
4 6
1 2
1 3
1 4
2 3
2 4
3 4
输出样例:
4

说明

对于 20% 的数据 n3

对于 40% 的数据 n5

对于 60% 的数据 n8

对于 80% 的数据 n1000

对于 100% 的数据 n100000 mmin(200000,n(n1)/2)

样例解释

选择边 12,13,23,34 ,能组成美妙的仙人掌,且不存在其它美妙仙人掌有超过 4 条边。


solution

考试的时候一脸懵逼,woc这什么东西。

于是开始想各种奇怪的骗分,n3 的时候好像可以手玩。

n5 好像也可以手玩。

就这样活生生把前 40 分搞成了提交答案题。

最后脑洞大开,强行给每条边加一个负边权,跑一遍spfa,然后找到第一个入队次数超过 n 的节点,统计和这个点有关系的边数。

结果全天下都是 40,lz 10 分。fuck

zyh输出 n 得了30 分,GG。

  • 下面才是题解

  • 观察美丽的仙人掌的定义,发现编号为 i i+1 的点之间必存在一条边

  • 问题转化成有若干区间,求最多的区间,使得区间之间没有重叠和覆盖。

  • 然后贪心或者dp都可以。

  • 看不懂吗?Emmm 我也看不懂

code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 100010

template<typename T>
inline void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

int f[MAXN],g[MAXN];
//g[i]表示和i相连的边中,最大的起点编号。
int main() {
    int n,m;
    input(n),input(m);
    while(m--) {
        int u,v;
        input(u),input(v);
        if(u>v) swap(u,v);
        if(u+1!=v) g[v]=max(g[v],u);
    }
    f[0]=-1;
    for(int i=2;i<=n;i++)
        f[i]=max(f[i-1],f[g[i]]+1);
    printf("%d",f[n]+n-1);
    //n-1是链上的那些边
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值