2018 ACM-ICPC南京网络赛



前言

去了一周秦皇岛回来就发生了鬼故事
离散讲完了……概率统计也讲完了……大物也讲完了……
才去一周啊怎么都讲完了 T^T
补作业补到死_(:з」∠)_
回来谈谈今年的南京网赛,感觉是被带偏榜了,线段树那道题目竟然没看到????死磕不会做的题目???
菜是原罪!
这次写8题,其中好像有两三题之前写过了,直接copy,剩下的4题如果题解看得懂再说吧
还有最近太忙了,不能维持2-3天一篇的速度,各位见谅_(:з」∠)_

A - An Olympian Math Problem


题意
sum(i -> n-1)(i * i!) % n 的值
思路 —— 打表
签到题,打表找规律,结果是 n - 1

  #include <iostream>
  using namespace std;

  int main(){
      int t;
      cin >> t;
      while(t--){
          long long n;
          cin >> n;
          cout << n - 1 << endl;
      }
  }


L - Magical Girl Haze


题意
给定一幅图,求将K条边置为0后,从点1到点n的最短路的值
思路 —— 最短路 + 状压思想
借助状压DP的思想,在跑Dijkstra的时候在d数组上多开一维记录下在删除k条边时到达这一点的最短路的值,队列中也是多开一维记录下当前已经删除了k条边,再进入进入下一个点时,可以不删边进入,在当前还能继续删边时,还要再加入一个删边进入了的点
另外,在队列中让删边多的点先出队列能提高运行效率(不确定不这样做会不会TLE)
最后,答案是d[n][k]

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <queue>
  using namespace std;
  typedef long long ll;
  typedef pair<int, int> pii;
  const int N = 1e5 + 3;
  const int M = 2e5 + 3;
  const ll inf = 1LL << 60;

  struct edge{
      int v, w, nxt;
  };
  struct Node{
      int u;
      ll  w;
      int k;
      bool operator < (const Node& b) const{
          if(w != b.w)    return w > b.w;
          else            return k < b.k;
      }
  };
  priority_queue<Node> que;
  ll   d[N][12];
  edge e[M];
  int  head[N];
  int  tot;

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


  inline void init(){
      memset(head, -1, sizeof(head));
      tot = 0;
  }

  inline void addEdge(int u, int v, int w){
      e[tot] = edge{v, w, head[u]};
      head[u] = tot++;
  }

  ll dijkstra(int n, int lim){
      while(!que.empty())     que.pop();
      fill(d[0], d[N - 1] + 12, inf);
      d[1][0] = 0;

      que.push(Node{1, 0, 0});
      while(!que.empty()){
          Node nd = que.top();
          que.pop();
          int u = nd.u, w = nd.w, k = nd.k;

          if(w > d[u][k])   continue;
          if(u == n)        return w;

          for(int i = head[u]; ~i; i = e[i].nxt){
              int v = e[i].v;
              if(d[v][k] > d[u][k] + e[i].w){
                  d[v][k] = d[u][k] + e[i].w;
                  que.push(Node{v, d[v][k], k});
              }
              if(k + 1 <= lim && d[v][k + 1] > d[u][k]){
                  d[v][k + 1] = d[u][k];
                  que.push(Node{v, d[v][k + 1], k + 1});
              }
          }
      }
      return inf;
  }

  int main(){
      int t = read();
      while(t--){
          int n = read(), m = read(), k = read();
          init();
          while(m--){
              int u = read(), v = read(), w = read();
              addEdge(u, v, w);
          }
          printf("%lld\n", dijkstra(n, k));
      }
  }


J - Sum


题意
定义一个数为square-free是该数无因数是平方数,定义f(i)为一个数i分解为两个数(a,b)且a和b都不是平方数的分解方案,这里(a,b)和(b,a)视为两种方案,求sum(f(i))
思路 —— 线性筛
可以知道f(i)是一个积性函数(可以打表观察出)
那么最后要解决的就是如何计算f(p^k)的问题
当k = 1时,f(p^k) = 2
当k = 2时,f(p^k) = 1
当k >= 3时,f(p^k) = 0
下面的代码差点TLE = =

  #include <iostream>
  #include <cstdio>
  #include <list>
  #include <cstring>
  #include <algorithm>
  #include <functional>
  using namespace std;
  const int N = 2e7 + 5;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  ll f[N], sum[N];
  bool isprime[N];
  int  prime[N], tot;
  int  cnt[N];

  int quickPow(int a, int b){
      int ans = 1, base = a;
      while(b){
          if(b&1)     ans = ans * base;
          base = base * base;
          b >>= 1;
      }
      return ans;
  }

  void init(){
      memset(isprime, true, sizeof(isprime));
      tot = 0, f[1] = 1, sum[1] = 1;
      for(int i = 2; i < N; i++){
          if(isprime[i]){
              prime[tot++] = i;
              cnt[i]++;
          }
          for(int j = 0; j < tot && i * prime[j] < N; j++){
              isprime[i * prime[j]] = false;
              if(i % prime[j] == 0){
                  cnt[i * prime[j]] = cnt[i] + 1;
                  break;
              }else{

                  cnt[i * prime[j]] = 1;
              }
          }
      }

      for(int i = 0; i < tot && (ll)prime[i] * prime[i] < N; i++){
          f[prime[i] * prime[i]] = 1;
          ll cur = (ll)prime[i] * prime[i] * prime[i];
          while(cur < N){
              f[cur] = 0;
              cur = cur * prime[i];
          }
      }

      for(int i = 2; i < N; i++){
          if(isprime[i]){
              f[i] = 2;
          }
          for(int j = 0; j < tot && i * prime[j] < N; j++){
              if(i % prime[j] == 0){
                  int pk = quickPow(prime[j], cnt[i * prime[j]]);
                  f[i * prime[j]] = f[pk] * f[i * prime[j] / pk];
                  break;
              }else{
                  f[i * prime[j]] = f[i] * f[prime[j]];
              }
          }
          sum[i] = f[i] + sum[i - 1];
      }
  }

  int main(){
      init();
      int t;
      scanf("%d", &t);
      while(t--){
          int n;
          scanf("%d", &n);
          printf("%lld\n", sum[n]);
      }
  }


B - The writing on the wall


题意
给定一个 N * M 的充满 1 * 1 的格子的矩形,将多个给定的格子涂黑后,求不含黑格子的矩形总个数 (1 <= N <= 10^5, 1 <= M <= 100)
思路 —— 暴力
枚举每一个点作为矩形的右上点的情况,那么可以用O(n^3)的方法将矩形统计出来,具体方法是先双重循环枚举点作为右上点的情况,在往回扫描(即再套一层循环)动态维护每一列的黑格子出现在遍历过的哪一行,并不断更新可行的矩阵的高并滚动更新答案,观察到M最大只是100,因此可以选择O(N*M*M)的遍历方式完成以上的过程

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  using namespace std;
  typedef long long ll;

  const int N = 1e5 + 15;
  const int M = 100 + 15;

  bool tag[N][M];
  int  p[N];

  void init(int n, int m){
      for(int i = 1; i <= n; i++){
          for(int j = 1; j <= m; j++){
              tag[i][j] = false;
          }
      }
      memset(p, 0, sizeof(p));
  }

  int main(){
      int t, csn = 1;
      scanf("%d", &t);
      while(t--){
          int n, m, k;
          scanf("%d%d%d", &n, &m, &k);
          init(n, m);
          while(k--){
              int x, y;
              scanf("%d%d", &x, &y);
              tag[x][y] = true;
          }


          ll sum = 0;
          for(int i = 1; i <= n; i++){
              for(int j = 1; j <= m; j++){
                  if(tag[i][j]){
                      p[j] = i;
                  }
              }

              for(int j = 1; j <= m; j++){
                  int h = i;
                  for(int k = j; k >= 1; k--){
                      h = min(h, i - p[k]);
                      sum += h;
                  }
              }
          }
          printf("Case #%d: %lld\n", csn++, sum);
      }
  }


E - AC Challenge


题意
给定N个任务,每个任务可能需要先完成前置任务,每个任务完成的得分是t*a[i]+b[i],现在需要安排合理的任务完成顺序,使完成任务的总得分最大
思路 —— 状压DP
定义DP状态为完成哪些任务获得的得分最大值dp[st],则dp[st] = max{dp[prest] + a[j]*getSt(st) + b[j]},其中j为当前枚举的任务,prest = st ^ j,注意要处理一下st使其合法,所谓合法就是要满足前置任务的要求

  #include <iostream>
  #include <cstdio>
  #include <vector>
  #include <cstring>
  #include <algorithm>
  #include <functional>
  using namespace std;
  const int N = 20 + 1;
  typedef long long ll;
  const ll inf = 1LL << 60;

  int a[N], b[N];
  int lim[N];
  int arrst[1 << N], pst;
  bool ok[1 << N];
  ll  dp[1 << N];

  void getSt(int n){
      pst = 0;
      memset(ok, false, sizeof(ok));
      for(int st = 0; st < (1 << n); st++){
          bool flag = true;
          for(int j = 0; j < n; j++){
              if((st & (1 << j)) && ((st & lim[j]) != lim[j])){
                  flag = false;
                  break;
              }
          }
          if(flag){
              arrst[pst++] = st;
              ok[st] = true;
          }
      }
  }

  int getCnt(int x){
      int cnt = 0;
      while(x){
          cnt++;
          x -= (x & -x);
      }
      return cnt;
  }

  int main(){
      int n;
      while(~scanf("%d", &n)){
          memset(dp, 0, sizeof(dp));
          for(int i = 0; i < n; i++){
              scanf("%d%d", &a[i], &b[i]);
              lim[i] = 0;
              int k;
              scanf("%d", &k);
              while(k--){
                  int tmp;
                  scanf("%d", &tmp);
                  lim[i] += (1 << (tmp - 1));
              }
          }
          getSt(n);

          ll ans = -inf;
          for(int i = 0; i < pst; i++){
              int st = arrst[i];
              int cnt = getCnt(st);
              for(int j = 0; j < n; j++){
                  if((st & (1 << j)) == 0)        continue;
                  if(!ok[st^(1 << j)])            continue;
                  dp[st] = max(dp[st], dp[st^(1 << j)] + a[j]*cnt + b[j]);
                  ans = max(ans, dp[st]);
              }
          }
          printf("%lld\n", ans);
      }
  }


G - Lpl and Energy-saving Lamps


题意
已知有N个房间,每个房间有k[i]个灯泡需要更换。现在每个月都买入 m 个灯泡,从编号小的房间开始换灯泡,如果其需要更换的数量超过现有数量,则跳过,直到找到第一个数量不超过现有数量的房间进行更换,如果更换后还有剩余则继续寻找,如果都不能更换则等到下一个月。现在给定q个询问,每个询问输出第d个月有多少个房间灯泡已经更换完毕,以及当前还有多少个灯泡
思路 —— 线段树单点更新
为什么比赛的时候没看到这道题 T^T
记当前灯泡数量为b,开线段树并直接枚举月份,循环询问比b小的数,若有则更新b,并将该房间灯泡设为inf,直到无跳出询问并保存已更换数量以及b,进入下一月份,最后对于询问只需要输出保存的结果即可

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  using namespace std;
  #define lson l, m, rt << 1
  #define rson m + 1, r, rt << 1 | 1
  const int N = 1e5 + 5;
  const int inf = 0x3f3f3f3f;

  int seg[N << 2], a[N];
  int ans_room[N * 40], ans_lamp[N * 40];

  void pushUp(int rt){ seg[rt] = min(seg[rt << 1], seg[rt << 1 | 1]); }
  void build(int l, int r, int rt){
      if(l == r){
          seg[rt] = a[l];
          return;
      }
      int m = (l + r) >> 1;
      build(lson);
      build(rson);
      pushUp(rt);
  }
  int query(int val, int l, int r, int rt){
      if(seg[rt] > val)   return -1;
      if(l == r)          return l;
      int m = (l + r) >> 1;
      if(seg[rt << 1] <= val)      return query(val, lson);
      else                         return query(val, rson);
  }
  void update(int p, int l, int r, int rt){
      if(l == r){
          seg[rt] = inf;
          return;
      }
      int m = (l + r) >> 1;
      if(p <= m)  update(p, lson);
      else        update(p, rson);
      pushUp(rt);
  }

  int main(){
      int n, m;
      while(~scanf("%d%d", &n, &m)){
          ans_room[0] = ans_lamp[0] = 0;
          for(int i = 1; i <= n; i++){
              scanf("%d", &a[i]);
          }
          build(1, n, 1);

          int month = 1, lamp = m;
          while(true){
              if(seg[1] == inf)   break;

              int cnt = 0;
              while(true){
                  int pos = query(lamp, 1, n, 1);
                  if(pos == -1)   break;
                  lamp -= a[pos];
                  update(pos, 1, n, 1);
                  cnt++;
              }
              ans_room[month] = ans_room[month - 1] + cnt;
              ans_lamp[month] = lamp;
              month++;
              lamp += m;
          }

          int q;
          scanf("%d", &q);
          while(q--){
              int x;
              scanf("%d", &x);
              x = min(x, month - 1);
              printf("%d %d\n", ans_room[x], ans_lamp[x]);
          }
      }
  }


I - Skr


题意
求回文串转数字和
思路 —— 回文树
回文树模板题

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  using namespace std;
  const int N = 2e6 + 5;
  const int inf = 0x3f3f3f3f;
  const int MOD = 1e9 + 7;
  typedef long long ll;

  char s[N];
  int  powten[N];

  void init(){
      powten[0] = 1;
      for(int i = 1; i < N; i++){
          powten[i] = (ll)powten[i - 1] * 10 % MOD;
      }
  }

  struct Node{
      int slink, len, cnt;
      int nxt[10];
  };

  struct PalindromicTree{
      int  tot;
      int  cursuffix;
      Node tree[N];
      int  num[N];

      void init(){
          num[0] = num[1] = 0;
          tree[0].slink = 0, tree[0].len = -1, tree[0].cnt = 1;
          tree[1].slink = 0, tree[1].len = 0,  tree[1].cnt = 1;
          tot = 2, cursuffix = 1;
          initNode(1);
          initNode(0);
      }

      void initNode(int o){
          memset(tree[o].nxt, -1, sizeof(tree[o].nxt));
      }

      ll  add(int pos){
          int idx = s[pos] - '0';
          int cur = cursuffix;
          while(true){
              int curlen = tree[cur].len;
              if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen])      break;
              cur = tree[cur].slink;
          }

          if(tree[cur].nxt[idx] != -1){
              cursuffix = tree[cur].nxt[idx];
              return 0;
          }

          int nxt = tree[cur].nxt[idx] = tot++;
          initNode(nxt);
          tree[nxt].len = tree[cur].len + 2;
          tree[cur].nxt[idx] = nxt;
          cursuffix = nxt;

          if(tree[nxt].len == 1){
              tree[nxt].cnt = 1;
              tree[nxt].slink = 1;
              return num[nxt] = s[pos] - '0';
          }

          num[nxt] = ((ll)num[cur] * 10 + (ll)(s[pos] - '0') * (powten[tree[nxt].len - 1] + 1)) % MOD;
          while(true){
              cur = tree[cur].slink;
              int curlen = tree[cur].len;
              if(pos - 1 - curlen >= 0 && s[pos] == s[pos - 1 - curlen]){
                  tree[nxt].slink = tree[cur].nxt[idx];
                  break;
              }
          }

          return num[nxt];
      }
  };

  PalindromicTree pt;

  int main(){
      init();
      pt.init();

      ll ans = 0;
      scanf("%s", s);
      for(int i = 0; s[i]; i++){
          ans = (ans + pt.add(i)) % MOD;
      }
      printf("%lld\n", ans);
  }


K - The Great Nim Game


题意
有N堆石子(N为大数),每堆的个数按给定公式生成,问去掉若干堆后进行Nim博弈,先手必胜的方式有多少种
思路 —— Nim博弈 + 快速幂 + 乘法逆元 + 线性基
首先是Nim博弈结论,异或和为0则必败,因此原题目的反面等价于求异或和为0的组合种数(记为b),答案则为2^n - b
异或和为0的组合种数可以用线性基求解。首先k最大只有4096,因此用公式求出的值的循环节最大也就4096,而构造线性基的话,循环节以外的数,都能被这一循环节的数异或得到0,因此对构造出来的线性基没有影响。知道了这一点,便可将可判定出现循环节为止的数拿去构造线性基,进而求出线性基的秩。以前写二进制那篇文章,里面有一个结论“异或得到的数重复2^(n-|B|)次”,这里的|B|是线性基的秩,因此0也会出现这么多次,于是我们需要得到的答案就是2^n - 2^(n-|B|)
最后的问题就是,2^n中n是个大数,不能直接套快速幂,怎么办?只需要将其转化为

ans = 2
for i = 0 to n.size()
  ans = (quickPow(ans, 10) * quickPow(base, n[i] - '0')) % MOD

即可
另外好像更多人是用DP过的?有机会再补

  #include <cstdio>
  #include <algorithm>
  #include <cstring>
  #include <vector>
  #include <iostream>
  using namespace std;
  const int N = 1e7 + 15;
  typedef long long ll;
  const ll MOD = 1000000007;
  const int HIGHEST = 12;

  char s[N];

  int b[HIGHEST + 5];
  bool used[5000];

  int build(vector<int>& a, int n){
      memset(b, 0, sizeof(b));
      for(int i = 0; i < n; i++){
          for(int j = HIGHEST; j >= 0; j--){
              if((a[i] >> j & 1) == 0)    continue;
              if(b[j])    a[i] ^= b[j];
              else{
                  b[j] = a[i];
                  for(int k = j - 1; k >= 0; k--)         if(b[k] && (b[j] >> k & 1))   b[j] ^= b[k];
                  for(int k = j + 1; k <= HIGHEST; k++)   if(b[k] >> j & 1)             b[k] ^= b[j];
                  break;
              }
          }
      }
      int ret = 0;
      for(int j = 0; j <= HIGHEST; j++){
          if(b[j])    ret++;
      }
      return ret;
  }

  ll quickPow(ll a, ll b, ll m){
      ll ans = 1, base = a;
      while(b){
          if(b&1)     ans = (ans % m * (base % m)) % m;
          base = (base % m * (base % m)) % m;
          b >>= 1;
      }
      return ans;
  }

  ll extgcd(ll a, ll b, ll& x, ll& y){
      ll d = a;
      if(!b){
          x = 1, y = 0;
      }else{
          d = extgcd(b, a%b, y, x);
          y -= (a/b) * x;
      }
      return d;
  }

  ll getInv(ll a, ll m){
      ll x, y;
      extgcd(a, m, x, y);
      return (x % m + m) % m;
  }

  ll toNum(){
      ll ret = 0;
      for(int i = 0; s[i]; i++){
          ret = ret * 10 + s[i] - '0';
      }
      return ret;
  }


  int main(){
      ll a, b, c, d, e, k, x1;
      vector<int> vec;
      while(~scanf("%s%lld", s, &x1)){
          vec.clear();
          memset(used, false, sizeof(used));
          scanf("%lld%lld%lld%lld%lld%lld", &a, &b, &c, &d, &e, &k);
          vec.push_back(x1);
          used[x1] = true;
          while(true){
              ll xpre = vec[vec.size() - 1];
              ll res = ((a * xpre * xpre  * xpre * xpre % k + b * xpre * xpre * xpre + c * xpre * xpre + d * xpre + e - 1) % k + k) % k + 1;
              if(used[res])   break;
              vec.push_back(res);
              used[res] = true;
          }

          ll ans = 1;
          for(int i = 0; s[i]; i++){
              ans = (quickPow(ans, 10, MOD) * quickPow(2, s[i] - '0', MOD)) % MOD;
          }

          int cnt = strlen(s) <= 6 ? build(vec, min((ll)vec.size(), toNum())) : build(vec, vec.size());

          ll tmp = ans;
          tmp = (tmp * getInv(quickPow(2, cnt, MOD), MOD)) % MOD;

          printf("%lld\n", (ans - tmp + MOD) % MOD);
      }
  }