8.22 Test——NOIP模拟题#1

T1:Count

题面:

【问题描述】

李华终于逃离了无尽的英语作文, 重获自由的他对一棵树产生了兴趣。 首先,他想知道一棵树是否能分成大小相同的几块(即切掉一些边,使得每个连通块的点数相同)。然后,他觉得这个问题过于简单,于是他想知道一共有多少种方案可以把这棵树分成大小相同的几块。

然后他发现自己不会了,于是向聪明的你求助。

【输入格式】

  第一行一个数,表示数的大小。   第二行至第N行,每行两个数x,y表示x和y之间有一条边。

【输出格式】

  一行,表示方案数。

【样例一输入】

  6

1  2

2  3

2  4

4  5

5  6

【样例一输入】

 3

HINT:

对于30%的数据,1<=n<=100。 对于60%的数据,1<=n<=100000。

对于100%的数据,1<=n<=1000000。

解析:

  比较基础的一道题吧,显然枚举块的大小,块的大小一定为树的大小的约数, 枚举从$1$到树的$size$之间的数, 如果是约数就去树上$check$, 设该树为$s$, 只有某子树的大小恰好为$s$时, 会形成一个大小为$s$的块, 将该子树的$size$改为0, 原树就被割小了, 再进行相同操作即可。我们发现割树的方式是唯一的(大小为$s$子树的分割方式只有一种),因此如果一棵树可以被分割成多个大小为$s$的块,它对答案的贡献只有1

 代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1000004;

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

int n, ans = 1;

int head[maxn], tot;
struct edge{
    int nxt, to;
}e[maxn<<1];

void Addedge(int x, int y)
{
    e[++tot] = (edge){head[x], y};
    head[x] = tot;
}

int siz[maxn], nowsiz[maxn];

void dfs(int x, int fa)
{
    siz[x] = 1;
    for(int i = head[x]; i; i = e[i].nxt)
    {
        int id = e[i].to;
        if(id == fa)    continue;
        dfs(id, x);
        siz[x] += siz[id];
    }
}

int blo;

void dfs2(int x, int s, int fa)
{
    if(siz[x] < s)
    {
        nowsiz[x] = siz[x];
        return ;    
    }
    nowsiz[x] = 1;
    for(int i = head[x] ; i; i = e[i].nxt)
    {
        int id = e[i].to;
        if(id == fa)    continue;
        dfs2(id, s, x);
        nowsiz[x] += nowsiz[id];
        if(nowsiz[id] > s)
            return ;
    }
    if(nowsiz[x] == s)
    {
        nowsiz[x] = 0;
        blo ++;
    }
}

int check(int x)
{
    blo = 0;
    dfs2(1, x, 0);
    return (blo == n/x);
}

int main()
{
    freopen("count.in", "r", stdin);
    freopen("count.out", "w", stdout);
    n = read();
    for(int i = 1; i < n; ++i)
    {
        int u = read(), v = read();
        Addedge(u, v);
        Addedge(v, u);
    }
    dfs(1, 0);
    for(int i = 2; i <= n; ++i)
        if(n % i == 0)
            ans += check(i);
    printf("%d\n", ans);
    return 0;
}
count

T2:Dinner

题面:

【问题描述】 

清儿今天请好朋友们吃饭,一共 N 个人坐在坐在圆桌旁。 吃饭的第一步当然是点餐了。服务员拿来了 M 份菜单。第 i 个人阅读菜单并点出自己喜欢的菜需要花费时间 T[i]。 当一个人点完菜之后,就会把菜单传到他右手边的第一个人。 M 份菜单是同时发出的,每个菜单只能同时被一个人阅读。 清儿希望知道如何分发菜单,才能让点餐的总时间花费最少呢?

【输入格式】  

输入文件名为 dinner.in

输入第一行是 N 和 M,表示人数和菜单数 输入第二行,N 个数,表示每个人点餐所需要的时间。

【输出格式】 

输出文件名为 dinner.out

输出一个整数表示点餐花费的最小时间。

【样例一输入】

3 2

1 5 10

 【样例一输出】

10

 【样例二输入】

4 2

1 2 3 4

 【样例二输出】

5

HINT:  对于 20%的数据,n<=100,m<=100.  对于 60%的数据,n<=10000,m<=100.

 对于 100%的数据,n<=50000,T[i]<=600,m<=3000.

解析:

   首先是基础操作, 把环断开, 再复制一遍序列在原序列后, 设该数列为$T[i]$

  要求最长的时间最短, 显然可以二分答案,如何$check$, 假设我们要$check$的数是$mid$

  很容易想到一个暴力的$check$, 枚举环的断点,向后跳,把环分成多个区间,每个区间的和 $ <= mid$, 最后把区间个数与$m$比较, 时间复杂度为$O(N^{2}logK)$(其中$K$为值域范围)

  然后也容易想到一个小优化, 根据常识, $T[i]$为正, 在枚举断点向后跳时,可以利用前缀和与二分在$logN$的时间内找到右端点,此时时间复杂度为$O(NMlogNlogK)$

  考场上我想到这里就跑路了,因为一开始读题时感觉第三题更可做,结果发现题读错了, 亏得不谈, 也算是个教训吧, 考完后我接着想了一下,想出了正确的解法(不是倍增)

  考虑我们的算法慢在哪里。我设$s[i]$为数组的前缀和, 若$s[r] <= mid$ && $s[r+1] > mid$, 对于一种序列的分割,在分割完成后就被分成了多个区间, 而必定有一个区间的左端点出现在$[1, r + 1]$内, 因此只需要枚举$[1, r + 1]$即可。为什么$[r + 2, n]$就不用枚举了呢, 因为在其中任意一个点开始分割,一定会跳到$[1, r + 1]$中, 设跳到的点为$p$, 所分割出来的块的个数与从点$p$开始分割的个数是一样的, 这就完成了时间复杂度的优化。

  为什么要区间是$[1, r + 1]$而不是$[1, r]$呢?因为原问题是一个环,虽然$s[r] <= mid$ && $s[r+1] > mid$, 但也有可能$T[n] + s[r] <= mid$ && $s[r+1] > mid$, 这样的式子还可以写下去,简而言之就是$[1, r]$的左端点可以向左延伸(原问题是环),但右端点不行,无论我从点$1$左边的哪个点开始跳, 有可能跳过$[1, r]$, 但也一定会在$r + 1$点停下,这样正确性就可以保证了

  最终的时间复杂度是$O(SizeMlogNlogK)$(其中$Size$是第一个块的大小, 即$r + 1$), 实际上远远达不到这个上界, 有些时候不用跳$m$次就跳完序列了, 这种时候$Size$都不用枚举完, 反正随机数据是随便过的, 刻意构造的话就不得而知了

 代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 50004;

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

int n, m, mx, ans, sum, a[maxn<<1], s[maxn<<1];

int search(int x, int l, int r)
{
    int t = l - 1, mid, ret = l;
    while(l <= r)
    {
        mid = (l + r)>>1;
        if(s[mid] - s[t] <= x)
            ret = mid, l = mid + 1;
        else
            r = mid - 1;
    }
    return ret;
}

bool check(int x)
{
    int q = search(x, 1, n);
    for(int i = 1; i <= q + 1; ++i)
    {
        int k = 0, j = i;
        while(j < n + i)
        {
            int p = search(x, j, n + i - 1);
            k ++;
            j = p + 1;
            if(k > m)    break;
        }
        if(k <= m)
            return 1;
    }
    return 0;
}

int main()
{
    freopen("dinner.in", "r", stdin);
    freopen("dinner.out", "w", stdout);
    n = read();m = read();
    for(int i = 1; i <= n; ++i)
    {
        a[i] = a[n+i] = read();
        mx = max(mx, a[i]);
        sum += a[i];
    }
    for(int i = 1; i <= (n<<1); ++i)
        s[i] = s[i-1] + a[i];
    int l = mx, r = sum, mid;
    while(l <= r)
    {
        mid = (l + r)>>1;
        if(check(mid))
            ans = mid, r = mid - 1;
        else
            l = mid + 1;
    }
    printf("%d\n", ans);
    return 0;
}
dinner

T3:Chess

题面:

【问题描述】   pig 在下象棋的时候特别喜欢用马,他总是计算着自己的马还需要几步才能吃掉对方的帅,以及方案数的个数,当然 pig 很笨,所以他只能求助于你。   我们假设在 m*n 的棋盘上,pig 的马只能走 1*2 的格点,他的马一开始在 st,对方的帅在 ed。当然,我们不能忽视友军和敌军,所以如果落点被友军占有,那么就不能飞过去了;如果落点被敌军占有,那么 pig 认为自己这一步赚了,所以不计入总步数。为了简化问题,我们的马在飞的时候不受到敌军限制

【输入格式】  

第一行两个数 m,n 表示棋盘大小为 m*n。   接下来 m 行,每行 n 个数字, 0 表示格子为空,1 表示格子上有敌军,2 表示格子上有友军,3 表示马的位置,4 表示敌方的帅。

【输出格式】

  两行。   第一行:一个数,最少情况下实际走的步数。如果没有方案存在,输出- 1。   第二行:一个数,达到最小值的方案总数,如果两个方案走的空格不同则认为这两个方案不同(详见样例)。这个数保证不超过内设64位整数(long long/ int64)的大小。如果第一行是-1,不要输出此行。

【样例一输入】

4 5 

0 0 0 0 1

0 0 3 0 0

0 0 0 4 0

0 1 0 0 0

【样例一输出】

0

1

【样例二输入】

4 5 

1 0 0 0 0

3 0 0 0 0

0 0 2 0 0

0 0 0 4 0

【样例二输出】

2

3

【样例二解释】

 一共有3种方案。X代表实际走的步数

10000 10X00 10X00

30X00 30000 3000X

00200 0X200 00200

0X040 00040 00040

 HINT:落在敌军的位置敌军就会被吃掉。不要想在两个位置来回跳跃。

  对于30%的数据,m,n<=5。 对于100%的数据,m,n<=50。

解析:

   一开始看错题了,结果只有30分

  还是直接说正解吧。其实就是最短路计数问题,但是需要一些转化。如果两个$0$点能直接相连, 就连一条权值为$1$的边。如果遇到没有访问过的$1$点,就去$dfs$一次,  把走一步能到的$0$点(包括先走几个$1$点才能到的$0$点)存入一个栈内,这些栈内的不同的点间也建一条权值为$1$的边, 把这些访问过的$1$点打上标记。最后在新图上跑最短路计数即可。

  这里的边权都为1,因此$bfs$即可。注意由于新图的边的条数不好计算,就根据空限尽量开大一点吧。还有我不知道是否有必要,感觉是必要的吧,也没试过,反正我是判了重边的

 代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2510;

int n, m, a[55][55], s[2], dis[maxn], ans, des;
int stak[maxn], top;
int q[maxn], h, t;
bool vis[55][55], g[maxn][maxn];
ll way[maxn], num;
int dx[8] = {1, 1, 2, 2, -1, -1, -2, -2};
int dy[8] = {-2, 2, -1, 1, 2, -2, 1, -1};

int head[maxn], tot;
struct edge{
    int nxt, to;
}e[maxn<<5];

void Addedge(int x, int y)
{
    e[++tot] = (edge){head[x], y};
    head[x] = tot;
}

int bfs()
{
    h = 1;
    q[++t] = (s[0] - 1) * n + s[1];
    while(h <= t)
    {
        int x = q[h++];
        if(x == des)
        {
            num = way[des];
            return dis[des] - 1;
        }
        for(int i = head[x]; i; i = e[i].nxt)
        {
            int id = e[i].to;
            if(dis[id] != -1)
            {
                if(dis[id] == dis[x] + 1)
                    way[id] += way[x];
            }
            else
            {
                dis[id] = dis[x] + 1;
                way[id] = way[x];
                q[++t] = id;
            }
        }
    }
    return -1;
}

void dfs(int x, int y)
{
    vis[x][y] = 1;
    for(int i = 0; i < 8; ++i)
    {
        int tox = x + dx[i], toy = y + dy[i];
        if(tox < 1 || tox > m)    continue;
        if(toy < 1 || toy > n)    continue;
        if(a[tox][toy] == 2)    continue;
        if(a[tox][toy] != 1)
            stak[++top] = (tox - 1) * n + toy;
        else if(!vis[tox][toy])
            dfs(tox, toy);
    }
}

int main()
{
    freopen("chess.in", "r", stdin);
    freopen("chess.out", "w", stdout);
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= m; ++i)
        for(int j = 1; j <= n; ++j)
        {
            char c;
            while((c=getchar())&&(c<'0'||c>'4'));
            a[i][j] = c - '0';
            if(a[i][j] == 3)
            {
                s[0] = i;
                s[1] = j;
                way[(i - 1) * n + j] = 1;
            }
            else
            {
                dis[(i - 1) * n + j] = -1;
                if(a[i][j] == 4)
                    des = (i - 1) * n + j;
            }
        }
    for(int i = 1; i <= m; ++i)
        for(int j = 1; j <= n; ++j)
        {
            if(a[i][j] == 2)    continue;
            int now = (i - 1) * n + j;
            for(int k = 0; k < 8; ++k)
            {
                int tox = i + dx[k], toy = j + dy[k];
                if(tox < 1 || tox > m)    continue;
                if(toy < 1 || toy > n)    continue;
                if(a[tox][toy] == 2)    continue;
                if(a[i][j] != 1 && !g[tox][toy])
                {
                    g[now][(tox - 1) * n + toy] = 1;
                    Addedge(now, (tox - 1) * n + toy);
                }
            }
        }
    for(int i = 1; i <= m; ++i)
        for(int j = 1; j <= n; ++j)
            if(a[i][j] == 1 && !vis[i][j])
            {
                top = 0;
                dfs(i, j);
                for(int p = 1; p <= top; ++p)
                    for(int k = 1; k <= top; ++k)
                        if(stak[p] != stak[k] && !g[stak[p]][stak[k]])
                        {
                            g[stak[p]][stak[k]] = 1;
                            Addedge(stak[p], stak[k]);
                        }
            }
    ans = bfs();
    printf("%d\n", ans);
    if(ans != -1)
        printf("%lld", num);
    return 0;
}
chess

猜你喜欢

转载自www.cnblogs.com/Joker-Yza/p/11395798.html