「NOI Online Round2」 题解

前言

没脑子又被吊打了,生气(T2签到题写了2.5h)。

赶紧订正。

A

不妨设\(p_1<p_2\),两种颜色对应\(1,2\)。容易发现既是\(p_1\)倍数有是\(p_2\)倍数的点肯定染成\(2\),因为前后都是\(1\)。由于\(>lcm\)的情况和\([1, lcm]\)一样,只需考虑\([1,lcm]\)。由于\(1\)十分密集,影响答案的段一定是连续的\(1\)

于是我们得找到\(y p_2 < x p_1 < (x + k - 1) p_1 < (y+1) p_2\),其中\(xp_1-yp_2\)得尽量小,\(k\)是极大值。

根据裴蜀定理,\(xp_1-yp_2\)最小值是\(\gcd(p_1,p_2)\)

最长连续段长度就是\(\dfrac{p_2 - 1 - \gcd(p_1, p_2)}{p_1} + 1\)

#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 1e6 + 50, mod = 1e9 + 7;
int p1, p2, k;
int gcd(int a, int b) {
   return !b ? a : gcd(b, a % b);
}
int main() {
   // freopen("color.in", "r", stdin);
   // freopen("color.out", "w", stdout);
   int t; scanf("%d", &t);
   while(t --) {
      scanf("%d%d%d", &p1, &p2, &k);
      if(p1 > p2) swap(p1, p2);
      if(k == 1) puts("No");
      else if(p1 == p2) puts("Yes");
      else {
         int g = gcd(p1, p2);
         puts((p2 - 1 - g) / p1 + 1 < k ? "Yes" : "No");
      }
   }
   fclose(stdin); fclose(stdout);
   return 0;
}

B

注意到\((x+1)^2=x^2+(2x+1)\),我们对于每个元素都考虑由他产生的\(2x+1\)对答案的贡献。

具体地,我们令\(p_i\)表示\(a_i\)上一次出现的位置(没有出现等于\(0\)),\(nxt_i\)表示下一次出现的位置(没有出现等于\(n+1\)),那对于位置\(u\),我们把答案加上\(\sum_{i = p_u}^u (2 c(i,u) - 1)(n-u+1)\),其中\(c(l,r)\)表示\([l,r]\)中不同数的个数。我们记\(q(p_u,u)=(n-u+1)\sum_{i = p_u}^u c(i,u)\),考虑求这个\(q\)

然后考虑每个位置对这些\(q\)询问的贡献。对于位置\(u\),他对\(q(l,r)\)有贡献,当且仅当\(l \leq u, u\leq r < nxt_u\),并且贡献是\((n-r+1)(u + 1 - l)\)。拆一下就是\((u + 1)(n - r + 1) - l (n-r+1)\),那我们把询问按\(l\)排序(你也可以通过nxt数组来免去排序),然后\(u=1 \rightarrow n\),把\(l\leq u\)\(q\)加入\(2\)个树状数组的\(r\)位置,这两个树状数组第一个维护\(n-r+1\),第二个维护\(l(n-r+1)\)。然后对于每个\(u\)通过在树状数组上询问\([u,nxt_u)\)把贡献加进去就行。

#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 1e6 + 50, mod = 1e9 + 7;
void upd(int &x, const int &y) {
   (x += y) >= mod ? x -= mod : 0;
}
int n, ans1, ans2, a[N], pre[N], p[N], nxt[N];
struct uni {

int a[N];
void build(int *b, int n) {
   for(int i = 1; i <= n; i ++) a[i] = b[i];
   sort(a + 1, a + n + 1);
   for(int i = 1; i <= n; i ++) {
      b[i] = lower_bound(a + 1, a + n + 1, b[i]) - a;
   }
}

} uq;
struct Bit {

int bit[N];
void add(int u, int val) {
   for(; u <= n; u += u & (-u)) {
      upd(bit[u], val);
   }
}
int qry(int u) {
   int ans = 0;
   for(; u >= 1; u &= u - 1) {
      upd(ans, bit[u]);
   }
   return ans;
}
int qry(int l, int r) {
   return (qry(r) - qry(l - 1) + mod) % mod;
}

} b0, b1;
struct Node {
   int l, r;
   bool operator < (const Node &b) const {
      return l < b.l;
   }
} an[N];
int main() {
   // freopen("sequence.in", "r", stdin);
   // freopen("sequence.out", "w", stdout);
   scanf("%d", &n);
   for(int i = 1; i <= n; i ++) {
      scanf("%d", a + i);
   }
   uq.build(a, n);
   for(int i = 1; i <= n; i ++) {
      p[i] = pre[a[i]]; pre[a[i]] = i;
      upd(ans1, (i - p[i]) * (n - i + 1ll) % mod);
   }
   fill(pre + 1, pre + n + 1, n + 1);
   for(int i = n; i >= 1; i --) {
      nxt[i] = pre[a[i]]; pre[a[i]] = i;
      an[i] = (Node) {p[i] + 1, i};
   }
   sort(an + 1, an + n + 1);
   int ans3 = 0, cur = 0;
   for(int i = 1; i <= n; i ++) {
      while(cur < n && an[cur + 1].l <= i) {
         cur ++;
         b0.add(an[cur].r, n - an[cur].r + 1);
         b1.add(an[cur].r, (n - an[cur].r + 1ll) * an[cur].l % mod);
      }
      upd(ans2, ((i + 1ll) * b0.qry(i, nxt[i] - 1) % mod - b1.qry(i, nxt[i] - 1) + mod) % mod);
   }
   ans1 = ((2ll * ans2 - ans1) % mod + mod) % mod;
   printf("%d\n", ans1);
   fclose(stdin); fclose(stdout);
   return 0;
}

C

我们用\(f(u)\)表示标记\(u\)对祖先 - 后代关系结点,剩下\(m-u\)对随便配对的方案。\(g(u)\)表示恰好有\(u\)对祖先 - 后代的方案数,题目要求\(g(0)\)\(g(m)\)

\[f(n)=\sum_{i = n}^m {i\choose n} g(i) \]

根据二项式反演,

\[g(n) = \sum_{i = n}^m {i\choose n} (-1)^{n - i} f(i) \]

这个\(f(n)\)很好求,\(dp[u][x]\)表示\(u\)子树标记\(x\)个祖先 - 后代关系的方案数(没标记的最后考虑,把阶乘乘上去)。先把子树背包合并,然后考虑选\(u\)

#include <algorithm>
#include <cstdio>
#include <vector>
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 5100, mod = 998244353;
int n, ans, fac[N], fav[N], dp[N][N], sz[2][N];
char s[N];
vector<int> G[N];
int C(int n, int m) {
   return 1ll * fac[n] * fav[m] % mod * fav[n - m] % mod;
}
int qpow(int a, int b) {
   int ans = 1;
   for(; b >= 1; b >>= 1, a = (ll) a * a % mod)
      if(b & 1) ans = (ll) ans * a % mod;
   return ans;
}
#define full(u) min(sz[0][u], sz[1][u])
void dfs(int u, int fa = 0) {
   sz[0][u] = s[u] == 0; sz[1][u] = s[u] == 1; dp[u][0] = 1;
   for(int i = 0; i < (int) G[u].size(); i ++) {
      int v = G[u][i];
      if(v != fa) {
         dfs(v, u);
         static int t[N];
         fill(t, t + full(u) + full(v) + 1, 0);
         for(int j = full(u); j >= 0; j --) if(dp[u][j]) {
            for(int k = full(v); k >= 0; k --) if(dp[v][k]) {
               (t[j + k] += 1ll * dp[u][j] * dp[v][k] % mod) %= mod;
            }
         }
         copy(t, t + full(u) + full(v) + 1, dp[u]);
         sz[0][u] += sz[0][v]; sz[1][u] += sz[1][v];
      }
   }
   for(int i = full(u) - 1; i >= 0; i --) {
      (dp[u][i + 1] += 1ll * dp[u][i] * (sz[s[u] ^ 1][u] - i) % mod) %= mod;
   }
}
int main() {
   // freopen("match.in", "r", stdin);
   // freopen("match.out", "w", stdout);
   scanf("%d%s", &n, s + 1); int m = n >> 1;
   for(int i = 1; i <= n; i ++) s[i] -= '0';
   fac[0] = 1;
   for(int i = 1; i <= m; i ++) fac[i] = 1ll * fac[i - 1] * i % mod;
   fav[m] = qpow(fac[m], mod - 2);
   for(int i = m; i >= 1; i --) fav[i - 1] = 1ll * fav[i] * i % mod;
   for(int i = 1; i < n; i ++) {
      int u, v;
      scanf("%d%d", &u, &v);
      G[u].pb(v); G[v].pb(u);
   }
   dfs(1);
   for(int i = 0; i <= m; i ++) {
      dp[1][i] = 1ll * dp[1][i] * fac[m - i] % mod;
   }
   for(int i = 0; i <= m; i ++) {
      int res = 0;
      for(int j = i; j <= m; j ++) {
         if((j - i) & 1) (res += mod - 1ll * dp[1][j] * C(j, i) % mod) %= mod;
         else (res += 1ll * dp[1][j] * C(j, i) % mod) %= mod;
      }
      printf("%d\n", res);
   }
   fclose(stdin); fclose(stdout);
   return 0;
}
posted @ 2020-04-25 18:08  hfhongzy  阅读(331)  评论(1编辑  收藏  举报