考试整理

又进行了一次NOIP模拟题。。。(day 1的题就赶脚要凉)

T1 大美江湖

大大的模拟。

所以他很简单。

测试点 1:
  0 次询问,所以直接freopen一下输出文件即可得分。期望得分 10 分。
测试点 4、5:
  由于保证人物不移动,而且不捡拾出生点的物资也不在出生点打怪,所以攻击防御一定全部为初始值,耗血一定为 0 ,直接输出 q 行数据即可。期望得分 20分。
测试点 6、7、8:
  地图上没有怪物,就不需要计算耗血那个复杂的式子,直接累加获得的药水即可。期望得分 30 分。
测试点2、3、9、10:
  考虑暴力模拟每一次行走,耗血的式子是可以 O(1) 计算的,而累加攻击防御也是 O(1) 的,于是总复杂地 O(q),可以通过本题。期望得分 40 分。
    需要注意的是在计算耗血的时候需要用到取整函数。如果使用 cpp 的 cmath库里面的 ceil() 函数的话,需要注意括号里相除两数不能全为 int。有关这个问题,https://www.luogu.org/blog/fusu2333/solution-p5006 里面有详细的讨论。

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<stack>
using namespace std;
int read() 
{
    int X=0,w=1; 
    char c=getchar();
    while(c<'0'||c>'9')
    { 
        if (c=='-')
        {
            w=-1; 
        } 
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        X=(X<<3)+(X<<1)+c-'0';
        c=getchar();
    } 
    return X*w;
}
int n,m,nx,ny;
int hp1,st1,de1,hp2,st2,de2;
char s[105][105];
int q,cz,fx;
int no,yes;
int main()
{
    freopen("mzq.in","r",stdin);
    freopen("mzq.out","w",stdout);
    n=read();
    m=read();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            s[i][j]=getchar();
        }
        s[i][m+1]=getchar();
    }
    hp1=read();st1=read();de1=read();
    nx=read();ny=read();
    st2=read();de2=read();
    q=read();
    for(int i=1;i<=q;i++)
    {
        cz=read();
        if(cz==2)
        {
            fx=read();
            if(fx==1)
            {
                ny--;
            }
            if(fx==2)
            {
                ny++;
            }
            if(fx==3)
            {
                nx--;
            }
            if(fx==4)
            {
                nx++;
            }
            if(s[nx][ny]=='R')
            {
                hp2=hp2-10;
                if(hp2<0)
                {
                    hp2=0;
                }
            }
            if(s[nx][ny]=='Q')
            {
                st2=st2+5;
            }
            if(s[nx][ny]=='Y')
            {
                de2=de2+5;
            }
            if(s[nx][ny]=='M')
            {
                no=hp1/(max(1,st2-de1));
                if(hp1%(max(1,st2-de1)))
                {
                    no++;
                }
                yes=max(1,st1-de2);
                hp2=hp2+max(1,no*yes);
            }
        }
        else
        {
            printf("%d %d %d\n",hp2,st2,de2);
        }
    }
    return 0;
}

T1 腐草为萤

 

 

子任务 1:
  只有一个点,所以只有 {1} 这一种集合,于是答案为 1。期望得分 5 分。
子任务 2、3:
  爆搜,枚举所有可能的集合,然后计算答案。由于每个点只有选进集合或不选两种可能,所以一共有 2n个集合,然后可以O(n) 的去检验集合是否合法,顺便统计答案。于是总复杂度 O(2n×n)。期望得分25分。
子任务 4、5:
  考虑 DP。设 fu 是以 u 为根的子树的答案。如果 u 没有孩子,那么 fu = uT
如果 u 只有一个孩子 v,那么要么选 u 不选 u 的子孙,要么不选 u。不选 u的答案即为 fv,选 u 的答案即为 uT。两种情况加起来就是 fu。
如果 u 有两个孩子 x,y。考虑要么选 u,要么只选 x 的子树内的元素,要么只选 y 的子树内的元素,要么既选 x 内的元素又选 y 内的元素但不选 u。前三种情况的答案易得。现在考虑第四种的答案。设 s 是 x 子树内的某个集合。考虑无论 y 的子树内怎么选,再加上 s 都是合法的,因为 y 和 x 之间没有祖先后代关系且 s 在 x 之内。设 gu 是以 u 为根能选择的集合个数,那么一共有 gy 个集合选择 s 以后依旧合法,设 s 的权值和为 ws,于是 s 的贡献即为 ws×gy。由于fx 为 x 子树内所有可能集合的权值和,所以可以发现 ∑xs= fw  。于是 x 子树内的集合对答案的总贡献是fx×gy。同理,y 子树内的集合对答案的贡献是 fy×gy。于是 fu=fy×gx+fx×gy+fx+fy+uT。gu=gx×gy+gx+gy+1。时间复杂度O(n),期望得分 30分。
子任务6、7:
  考虑在遍历子节点的时候,已经遍历了一些子节点,现在新加入了一个子节点。由于新加入一个子节点与之前遍历的子节点没有祖先后代关系,于是可以之前遍历过得子树看成一棵子树,然后问题就变成了子任务4、5。期望得分 40 分。需要注意的是由于读入规模达到了106左右,需要普通的读入优化。

Code:

#include <cstdio>

typedef long long int ll;

const int maxn = 1000005;
const int MOD = 1000000007;

template <typename T>
inline void qr(T &x) {
  char ch;
  do { ch = getchar(); } while ((ch > '9') || (ch < '0'));
  do { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } while ((ch >= '0') && (ch <= '9'));
}

int n, T;
int MU[maxn], frog[maxn], gorf[maxn];
bool vis[maxn];

struct Edge {
  int v;
  Edge *nxt;

  Edge(const int _v, Edge *h) : v(_v), nxt(h) {}
};
Edge *hd[maxn];

void dfs(const int u);

int main() {
  freopen("dzy.in", "r", stdin);
  freopen("dzy.out", "w", stdout);
  qr(n); qr(T);
  if (T) {
    for (int i = 1; i <= n; ++i) {
      MU[i] = i;
    }
  } else {
    for (int i = 1; i <= n; ++i) {
      MU[i] = 1;
    }
  }
  for (int i = 1, u, v; i < n; ++i) {
    u = v = 0; qr(u); qr(v);
    hd[u] = new Edge(v, hd[u]);
    hd[v] = new Edge(u, hd[v]);
  }
  dfs(1);
  printf("%d\n", frog[1] % MOD);
  return 0;
}

void dfs(const int u) {
  vis[u] = true;
  for (auto e = hd[u]; e; e = e->nxt) if (!vis[e->v]) {
    int v = e->v;
    dfs(v);
    frog[u] = (frog[u] * (gorf[v] + 1ll) % MOD) + (frog[v] * (gorf[u] + 1ll) % MOD);
    gorf[u] = (gorf[u] + gorf[v] + (1ll * gorf[u] * gorf[v])) % MOD;
  }
  frog[u] = (frog[u] + MU[u]) % MOD;
  ++gorf[u];
}

(诡异的zay码风。。。)

T3 锦鲤抄

子任务 1:

  点权都是0,于是无论怎么选答案都是 0,输出 0 即可。期望得分 5 分。

子任务 2:

  爆搜,枚举所有可能的顺序,然后计算答案。 由于保证了数据随机,可以在搜索的过程中进行剪枝,效率很高,期望得分25 分。

子任务 3:

  给出的是一个 DAG 。考虑对于一个 DAG 来说,一个良好的的性质就是在拓扑 序后面的点无论如何变化都无法影响到前面的点。这个题也一样。对于任意一个不 出现原图中本身入度为 0 的点的序列,只要按照拓扑序选点,就一定能取遍序列中 所有的点。 于是发现这张图上入度不为0的点事实上都可以被选择。于是我们把所有入度不 为0的点排一下序,求前k个就可以了。时间复杂度 O(nlogn),期望得分30。

子任务 4、5:

  考虑DAG的情况放到普通有向图上会发生什么。 有了子任务 3 的提示,我们可以考虑把整个图缩点,将其变成一个DAG来做。 对于一个DAG,显然可以通过子任务 3 调整顺序的方式使得每个强连通分量的 选择情况除选点个数以外互不影响。故下面只讨论一个强连通分量内部的情况。 一个强连通分量显然可以看作是一棵外向树加上很多边得到的。 一棵外向树的定义:一个外向树的任意一个节点要么为叶节点,要么它与孩子 间的所有边都是由它指向孩子。 一棵外向树显然是一个 DAG 。按照之前对 DAG 上情况的说明,显然我们可以 选择除了根节点以外的任意节点。 因为一个强连通分量内部是互相连通的,于是我们不妨钦定一个点为根。 对于一个没有入度的强连通分量,我们不妨钦定点权最小的点为根。这样显然 选择的是最优的。 对于一个有入度的强连通分量,我们不妨钦定那个有入度的点为根。这样在选 择到只剩根节点的时候,因为根节点有入度,所以根节点是可以被选择的。于是这 个强连通分量可以被全部选择。这显然是最优的。 这样综合上述讨论,有入度的强连通分量可以随便选,没有入度的强连通分量 去掉最小的点权的点。剩下贪心取前 k 个就可以了。 进行一次 tarjan的复杂度是 O(n+m),选前 k 个点可以排序一下。这样总复杂 度 O(m+nlogn),期望得分 35 分。注意到复杂度瓶颈在排序上,考虑我们只需要前 k 大而不需要具体前 k 个之间的大小关系,于是使用 std::nth_element()函数可 以将复杂度降至 O(n+m)。期望得分 40 分。注意,输入规模到了 10e7 级别,需要 fread 来实现读入优化。

Code:

#include <cstdio>
#include <algorithm>
#include <functional>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
  const int L = 1000000;
  char buf[L], *front=buf, *end=buf;
  char GetChar() {
    if (front == end) {
      end = buf + fread(front = buf, 1, L, stdin);
      if (front == end) return -1;
    }
    return *(front++);
  }
}

template <typename T>
inline void qr(T &x) {
  char ch = IPT::GetChar(), lst = ' ';
  while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
  while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
  if (lst == '-') x = -x;
}

const int maxn = 1000006;

struct Edge {
  int v;
  Edge *nxt;

  Edge(const int _v, Edge *h) : v(_v), nxt(h) {}
};
Edge *hd[maxn];

int n, m, k, vistime, top, scnt;
int MU[maxn], dfn[maxn], low[maxn], stack[maxn], belong[maxn], minv[maxn];
bool instack[maxn], haveind[maxn];

void tarjan(const int u);

int main() {
  freopen("zay.in", "r", stdin);
  freopen("zay.out", "w", stdout);
  qr(n); qr(m); qr(k); MU[0] = 2333;
  for (int i = 1; i <= n; ++i) qr(MU[i]);
  for (int i = 1, u, v; i <= m; ++i) {
    u = v = 0; qr(u); qr(v);
    hd[u] = new Edge(v, hd[u]);
  }
  for (int i = 1; i <= n; ++i) if (!dfn[i]) {
    tarjan(i);
  }
  for (int u = 1; u <= n; ++u) {
    for (auto e = hd[u]; e; e = e->nxt) if (belong[u] != belong[e->v]) {
      haveind[belong[e->v]] = true;
    }
  }
  for (int i = 1; i <= scnt; ++i) if (!haveind[i]) {
    MU[minv[i]] = 0;
  }
  std::nth_element(MU + 1, MU + 1 + k, MU + 1 + n, std::greater<int>());
  int ans = 0;
  for (int i = 1; i <= k; ++i) {
    ans += MU[i];
  }
  printf("%d\n", ans);
  return 0;
}

void tarjan(const int u) {
  dfn[u] = low[u] = ++vistime;
  instack[stack[++top] = u] = true;
  for (auto e = hd[u]; e; e = e->nxt) {
    int v = e->v;
    if (!dfn[v]) {
      tarjan(v);
      low[u] = std::min(low[u], low[v]);
    } else if (instack[v]) {
      low[u] = std::min(low[u], dfn[v]);
    }
  }
  if (dfn[u] == low[u]) {
    int v, &_mv = minv[++scnt];
    do {
      instack[v = stack[top--]] = false;
      belong[v] = scnt;
      if (MU[v] < MU[_mv]) _mv = v;
    } while (v != u);
  }
}

说实话,Tarjan听得我有点懵

转载于:https://www.cnblogs.com/gongcheng456/p/11071775.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值