Back

NOIP2009普及组题解

多项式输出

给一个存了多项式系数的数组,按一定规则输出多项式。

题解

模拟。

#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, a[N];
int main() {
  cin >> n;
  for (int i = n; i >= 0; i--) cin >> a[i];
  for (int i = n; i >= 0; i--) {
    if (a[i] == 0) {
      continue;
    }
    if (a[i] > 0) {
      if(i != n) {
        putchar('+');
      }
    } else {
      a[i] = -a[i];
      putchar('-');
    }
    if (i > 1) {
      if (a[i] != 1) {
        cout << a[i];
      }
      cout << "x^" << i;
    } else if (i == 1) {
      if (a[i] != 1) {
        cout << a[i];
      }
      cout << "x";
    } else {
      cout << a[i];
    }
  }
}

分数线划定

有n个选手,选出分数排行前150%*m的选手,分数相同顺延。

题解

模拟。

#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
int n, m;
struct P {
  int k, s;
  friend int operator<(P a, P b) {
    return a.s > b.s || a.s == b.s && a.k < b.k;
  }
}a[N];
int main() {
  cin >> n >> m;
  m += m / 2;
  for (int i = 1; i <= n; i++) {
    cin >> a[i].k >> a[i].s;
  }
  sort(a + 1, a + n + 1);
  for (int i = m; i <= n; i++) {
    if (a[i].s != a[m].s) {
      break;
    } else {
      m = i;
    }
  }
  cout << a[m].s << ' ' << m << endl;
  for (int i = 1; i <= m; i++) {
    cout << a[i].k << ' ' << a[i].s << endl;
  }
}

细胞分裂

求最小的$x$使得存在${m_1}^{m_2}|S^{x}$,如果不存在输出-1。

题解

先将${m_1}$质因数分解${m_1}=\prod{p_i^{n_i}}$,于是${m_1}^{m_2}=\prod{p_i^{n_im_2}}$。 如果有解,那么$S$必然包含$m_1$分解之后所有的质因数$p_i$。假设$S$对于每个$p_i$可以分解$q_i$个,如果${m_1}^{m_2}|S^{x}$,那么$p_i^{q_ix}|p_i^{n_im_2}$,得到不等式$q_i{x}\geq{n_im_2}$成立, 于是$x\geq \lceil\frac {n_im_2} {q_i}\rceil$, 对于S得到$x=\min \lceil\frac {n_im_2} {q_i}\rceil$

#include <bits/stdc++.h>
using namespace std;
const int N = 45000;
vector<int> p;
int n, m1, m2, h[N];
vector<pair<int,int>> decomposite(int x) {
  vector<pair<int,int>> res;
  for (int i = 0; i < p.size(); i++) {
    if (x % p[i] != 0) {
      continue;
    }
    int cnt = 0;
    while (x % p[i] == 0) {
      x /= p[i];
      cnt += 1;
    }
    res.push_back(make_pair(p[i], cnt));
  }
  if (x != 1) {
    res.push_back(make_pair(x, 1));
  }
  return res;
}
int main() {
  cin >> n >> m1 >> m2;
  for (int i = 2; i < N; i++) {
    if (h[i]) continue;
    p.push_back(i);
    for (int j = i + i; j < N; j += i) h[j] = 1;
  }
  vector<pair<int,int>> m = decomposite(m1), s;
  for (int i = 0; i < m.size(); i++) {
    m[i].second *= m2;
  }
  int ans = 1e9;
  for (int i = 1; i <= n; i++) {
    int x; cin >> x;
    s = decomposite(x);
    int mx = 0;
    for (int i = 0, j = 0; i < m.size(); i++) {
      while (j < s.size() && m[i].first != s[j].first) j++;
      if (j == s.size()) {
        mx = 1e9;
        break;
      }
      mx = max(mx, (m[i].second + s[j].second - 1) / s[j].second);
    }
    ans = min(ans, mx);
  }
  if (ans > 1e8) {
    puts("-1");
  } else {
    cout << ans << endl;
  }
}

道路游戏

有n段马路组成一条环形马路,m个单位时间,每个单位时间每段马路上都会出现不同数量的金币, 第i段马路出售花费$cost[i]$金币的机器人,机器人从第i段马路开始,每走过一段马路花费一个单位时间,同时收集该段 马路在当前时间的金币,最多可以走p段马路。每个单位时间有且只有一个机器人存在,问在扣除机器人的费用之后最多可以收集多少金币。

题解

DP,用$f[i]$表示到第$i$个时间最多能得到的金币数,转移方程为 $$f[i] = \max_{max(0, i-p)<=j<i,1<=k<=n}(f[j]+sum[k][j+1][i]-cost[k])$$ 其中$sum[k][j+1][i]$表示从j+1时间到i时间以第k段公路为起点获得的金币总数
直接暴力计算$sum$,时间复杂度$O(nmp^2)$

优化1

考虑用前缀和计算$sum[k][j+1][i]$。
$a[i][j]$表示第i段公路,第j单位时间出现的金币。
$s[i][j]$表示以第i段公路为起点,从第一个单位时间到第j个单位时间能获得的金币。
$$s[i][j]=s[i][j-1]+a[(i+j-1)\%n][j]$$ 用$t$表示第k段公路往前数j个的公路编号, 那么$s[t][i]-s[t][j]$表示从$t$开始$j$个单位时间之后能获取的金币数,而从$t$开始$j$个单位时间之后恰好是$k$, 即$s[t][i]-s[t][j]$为从j+1时间到i时间以第k段公路为起点共获得的金币
转移方程可以写为 $$f[i] = \max_{\substack{max(0, i-p)<=j<i \\ 0<=k<n}}(f[j]+s[t][i]-s[t][j]-cost[k])$$ 其中$t=((k-j)\%n+n)\%n$
通过前缀和优化得到$O(nmp)$的算法。

优化2

观察$f[i] = \max(f[j]+s[t][i]-s[t][j]-cost[k])$,考虑去掉t。
从$k$往后数$j$段公路,方程改写为 $$f[i] = \max_{\substack{max(0, i-p)<=j<i \\ 0<=k<n}}(f[j]+s[k][i]-s[k][j]-cost[(k+j)\%n])$$ 将$k$移到外层 $$f[i] = \max_{0<=k<n}{(s[k][i]+\max_{max(0, i-p)<=j<i}(f[j]-s[k][j]-cost[(k+j)\%n]))}$$ 可以发现$f[j]-s[k][j]-cost[(k+j)\%n]$是一个跟$i$无关的表达式。
令$$g[k][j]=f[j]-s[k][j]-cost[(k+j)\%n]$$ 得到 $$f[i] = \max_{0<=k<n}{(s[k][i]+\max_{max(0, i-p)<=j<i}g[k][j])}$$ 现在要求$\displaystyle\max_{max(0, i-p)<=j<i}g[k][j]$,这是一个经典的RMQ问题,可以用ST表等方法优化,
最后的时间复杂度为$O(nm\log{m})$。

优化3

令$j_1<j_2<i$,假设$g[k][j1] <= g[k][j2]$ ,那么$g[k][j1]$永远不会决策到,于是对于每个$g[k]$维护一个单调递减的队列。 时间复杂度$O(nm)$。

参考代码

$O(nmp)$的算法就a了,并没有写更多的优化。

#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int n, m, p;
int a[N][N], f[N], s[N][N], b[N];
int main() {
  cin >> n >> m >> p;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      scanf("%d", &a[i][j]);
    }
  }
  for (int i = 1; i <= n; i++) {
    scanf("%d", &b[i]);
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      s[i][j] = s[i][j-1] + a[(i+j-2)%n+1][j];
    }
  }
  memset(f, 0xc0, sizeof(f));
  f[0] = 0;
  for (int i = 1; i <= m; i++) {
    for (int j = max(1, i - p + 1); j <= i; j++) {
      for (int k = 1; k <= n; k++) {
        // (j, k) -> (1, ?)
        int t = ((k-(j-1))%n+n)%n;
        if (t == 0) t = n;
        f[i] = max(f[i], f[j - 1] + s[t][i] - s[t][j - 1] - b[k]);
      }
    }
  }
  cout << f[m] << endl;
}