牛客网NOIP赛前集训营 提高组(第七场)

中国式家长 2

链接:https://www.nowcoder.com/acm/contest/179/A
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

有一天,牛牛找到了一个叫《中国式家长》的游戏,游戏中需要靠"挖脑洞"来提升悟性。
挖脑洞在一个N行M列的地图上进行,一开始牛牛有K点行动力和0点悟性,地图上有两种格子:
1、悟性格: 挖悟性格需要减少10点行动力,如果行动力不到10点则无法挖取,挖取成功后悟性会提升10点
2、行动格: 挖取行动格没有行动力要求,挖取完之后行动力会增加一定点数,但行动力不能超过K(比如K = 150, 当前有140点行动力,挖取了一个可以增加20点行动力的格子,那挖取完成之后行动力会变成150)
有一些格子是开启的,另一些格子是关闭的,只允许挖取开启的格子。当一个格子挖取成功后,以它为中心的九宫格内的格子都会开启。(既第x行第y列的格子被挖取之后,第x - 1行和第x + 1行的第y - 1列,第y列,第y + 1列,第x行的第y - 1列,第y + 1列,这八个格子都会被开启)
每个格子都只允许被挖取一次。
现在给定地图的描述,以及牛牛的挖取顺序,牛牛希望你告诉它这个顺序是否可行,如果可行的话希望你计算出最终剩下多少点行动力,获得了多少点悟性。

输入描述:

第一行输入N, M, K代表地图有N行M列,一开始有K点行动力(游戏过程中行动力也不能超过K)
接下来N行每行M个非负整数,第i行的第j个非负整数aij描述第i行第j列格子,如果aij > 0说明这是一个行动格,挖取完之后会获得aij点行动力。否则说明这是一个悟性格。
接下来N行每行M个非负整数,第i行的第j个非负整数bij描述第i行第j列格子在最初是否开启,如果bij = 0说明这一格没有开启,否则则说明这一格开启了。
接下来一个正整数T代表牛牛一共挖取了T次
接下来T行每行两个整数,第i行的两个整数xi, yi代表牛牛的第i个操作:挖取第xi行第yi列的格子
 
对于100%的数据,1 <= N, M, K <= 200, 0 <= aij <= k, 0 <= bij <= 1, 1 <= T <= N * M, 1 <= xi <= N, 1 <= yi <= M

输出描述:

输出一行两个整数代表最终剩下的行动力点数、获得的悟性的点数

如果挖取过程不合法则输出一行-1 -1

挖取不合法有以下几种可能:

1、试图挖取一个没有开启的格子

2、试图重复挖取一个格子

3、行动力小于10的时候尝试挖取一个悟性格

只要挖取过程中的任何一步不合法,挖取过程就不合法
示例1

输入

复制
2 2 20
10 0
0 0
1 0
0 0
3
1 1
1 2
2 1

输出

复制
0 20

说明

一开始有20点行动力,0点悟性。第一步挖取第一行第一列的格子,这格是行动格,不需要消耗行动力,可以增长10点行动力,且一开始就是开启的,但由于行动力最多只能有K点,挖取完成后还是20点行动力,0点悟性。第一格挖取之后,剩下的格子都开启了,因此接下来两次挖取都是合法的,每次减少10点行动力,增加20点悟性。因此最后剩余0点行动力,20点悟性。
示例2

输入

复制
2 2 20
10 0
0 0
1 0
0 0
1
1 2

输出

复制
-1 -1

说明

第一行第二列的格子没有开启,所以挖取失败了
示例3

输入

复制
2 2 20
10 0
0 0
1 0
0 0
2
1 1
1 1

输出

复制
-1 -1

说明

一个格子只能挖取一次,因此第二次挖取失败了
示例4

输入

复制
2 2 20
10 0
0 0
1 0
0 0
4
1 1
1 2
2 1
2 2

输出

复制
-1 -1

说明

最后一次挖取时,行动力已经不够了,因此最后一次挖取失败了

/*
一道没什么细节的模拟... 
*/ 
#include<bits/stdc++.h>

#define N 207 

using namespace std;
int n,m,k,x,y,T,tmp,ans;
int a[N][N],can[N][N],vis[N][N];
int e[8][2]={{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};

inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int main()
{
    n=read();m=read();k=read();tmp=k;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
    a[i][j]=read();
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
    can[i][j]=read();
    T=read();int flag=0;
    while(T--)
    {
        x=read();y=read();
        if(!can[x][y]){flag=1;break;}
        if(vis[x][y]){flag=1;break;}
        if(a[x][y]<=0 && k<10){flag=1;break;}
        vis[x][y]=1;
        if(a[x][y]<=0) k-=10,ans+=10;
        if(a[x][y]>0) k+=a[x][y],k=min(k,tmp); 
        int xx,yy;
        for(int i=0;i<8;i++)
        {
            xx=x+e[i][0];yy=y+e[i][1];
            if(xx>0 && xx<=n && yy>0 && yy<=m) can[xx][yy]=1;
        }
    }
    if(flag) {printf("-1 -1\n");return 0;}
    else printf("%d %d\n",k,ans);
    return 0;
}

随机生成树

链接:https://www.nowcoder.com/acm/contest/179/B
来源:牛客网

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

牛牛在纸上画了N个点(从1到N编号),每个点的颜色用一个整数描述。牛牛决定用这N个点随机生成一棵树,生成的规则如下:
1、1号点是根节点
2、对于2号点到N号点,每个点随机指定一个父亲。i号点(2 <= i <= N)的父亲在i的约数中随机挑选一个。(例如10号点的父亲可以是1号,2号,5号,7号点的父亲只能是1号点)
树生成完之后,牛牛可以计算出这个树有多少个联通块,一个联通块是一些点的集合,需要满足以下两个条件:
1、从集合中任取两个点都满足:两个点颜色相同,且这两个点之间存在一条树边组成的路径,路径上的所有点都和这两个点颜色相同
2、对于集合中的任意一个点和集合外的任意一个点:两点要么不同色,要么不存在一条树边组成的路径使得路径上所有点都和这两个点同色。

牛牛希望计算出生成的树中最多有多少个联通块

输入描述:

第一行一个整数N代表点数
第二行N个整数,第i个整数ci代表第i个点的颜色
 
对于30%的数据, 2 <= N <= 10, 1 <= 颜色 <= 5
对于60%的数据, 2 <= N <= 5000, 1 <= 颜色 <= 5000
对于80%的数据, 2 <= N <= 200000, 1 <= 颜色 <= 10^9
对于100%的数据, 2 <= N <= 500000, 1 <= 颜色 <= 10^9

输出描述:

输出一个整数最多有多少联通块
示例1

输入

复制
4
1 1 1 2

输出

复制
2

说明

2号点和3号点的父亲一定是1,4号点父亲可能是1或者2,两种情况下联通块数都是2

/*
要求连通块尽量多。 
考虑贪心,则当前点如果能连到和它颜色不同的点就对答案有贡献。
对一个点扫他的倍数哦按段是否连边计算答案即可。 
*/
#include<bits/stdc++.h>

#define N 500001

using namespace std;
int n,m,ans,cnt,flag;
int col[N],vis[N];

inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++) col[i]=read();
    for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i)
    {
        if(col[j]!=col[i] && !vis[j]) {vis[j]=1;cnt++;} 
    }                
    cout<<cnt+1<<endl;
    return 0;
}

洞穴

链接:https://www.nowcoder.com/acm/contest/179/C
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

有一天,牛牛找到了一个巨大的洞穴。洞穴可以描述成一个有向图,一共有N个节点(从1到N编号)和M条长度为1的有向边,每条边从某一个节点u连向另一个节点v(u可能等于v)。
为了更好的探索洞穴,牛牛向你提出了Q个问题,每个问题给定两个节点a, b以及一个数l, 你需要告诉牛牛是否存在一条开始于a, 结束于b的路径,总长度为l。(路径中可以经过任意点、边多次,但只能沿着单向边给定的方向行走,不允许反向行走)。
 

 


代表牛牛的一个询问
 
20%的数据, 1 <= M <= 30, 1 <= li <= 5
50%的数据, 1 <= li <= 100
100%的数据, 1 <= N <= 100, 1 <= M <= 10000, 1 <= Q <= 1000, 1 <= li <= 10,1 <= ai, bi, ui, vi <= N

输出描述:

输出Q行依次回答牛牛的Q个询问
每行输出YES或NO,YES代表存在符合条件的路径,NO代表不存在
示例1

输入

复制
3 3
1 2
2 3
3 1
3
100 1 1
100 1 2
100 1 3

输出

复制
NO
YES
NO

说明

这个图是一个三元环,从1号点开始走100步之后必定停在2号点上

/*
首先思考暴力怎么写。
f[a][i][b]表示从a到b经i步是否可达。 
转移类似floyed ,枚举中间点c,用(f[a][i-1][c] &&  f[c][1][b])更新。
然后就发现i可以二进制拆分。
f[a][i][b]表示从a到b经2^i步可达,然后数据水就可以过掉这道题了2333
正解:因为f只有0,1两种状态,想到能用bitset优化。
首先预处理 若f[a][j][b]==1 则 f[a][j+1]|=f[b][j]
然后扫一遍各个系答案即可。 
复杂度大约是n^3 
*/
#include<bits/stdc++.h>

#define N 107
#define S 31

using namespace std;
int n,m,u,v,l,a,b,Q;
bitset<N>f[N][33],ans,tmp;

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++)
        scanf("%d%d",&u,&v),f[u][0][v]=1;
    for(int j=0; j<=S; j++) for(int i=1; i<=n; i++)
    {
        for(int k=1; k<=n; k++)
          if(f[i][j][k])f[i][j+1]|=f[k][j];
    }
    scanf("%d",&Q);
    while(Q--)
    {
        scanf("%d%d%d",&l,&a,&b);
        ans.reset();ans[a]=1;
        for(int i=0;i<=S;i++)
        {
            if(!(l>>i))break;
            if((l>>i)&1)
            {
                tmp=ans;
                ans.reset();
                for(int j=1; j<=n; j++)if(tmp[j])ans|=f[j][i];
            }
        }
        puts(ans[b]? "YES":"NO");
    }
    return 0;
}

 

posted @ 2018-10-28 19:21  安月冷  阅读(181)  评论(0编辑  收藏  举报