ICPC昆明区域赛·赛前挣扎复习题

HihoCoder - 1629 Graph (回滚莫队+并查集按秩合并)

题目:https://vjudge.net/contest/419697#problem/C

 

遇到了回滚莫队。

https://blog.csdn.net/a54665sdgf/article/details/82501086

开拓了眼界。

并查集随机删除是不行的,但是可以按顺序撤销,回滚莫队恰好可以支持这种操作。

 

 

[hihocoder 1635] Colored Nodes(思维+基环树)

https://blog.csdn.net/alan_cty/article/details/78713865

发现,虽然一轮之后,每个点的颜色不知道,但是知道这个点的颜色是从哪个点继承的

然后继承关系是一个基环树

只有环上的颜色最后F[i]值不为0

且环上每个颜色最后答案一样。

然后做一下就行

关键在于发现:颜色不知道,但是知道这个点的颜色是从哪个点继承的,然后转化为基环树问题。

 

 

D - Rikka with Subsequences

 

 https://blog.csdn.net/wxh010910/article/details/84950709

 (全网仅有的wxh的题解。。。。)

cnt^3怎么处理?

套路:用组合意义拆开。

问题转化成为,三个相同的串,从每个串中分别选出一个子序列,这三个子序列相同的方案数。(方案不一样,当且仅当存在从某个串中选出来的子序列的位置不一样)

但是还是很不好写。。。因为涉及到邻接矩阵的限制的问题。

(此处省略理解代码1.5h)

统计i,j,k位置结尾的合法子序列,必须要考虑上一个是不是能接上去,但是复杂度太高不能再枚举了。

所以先固定i,就知道了a[i],也就是b[j],c[k]必须等于a[i],否则没有意义。

很自然地设sum[j][k]表示,所有a中选<i的,b中选<j的,c中选<k的,且能直接拼在a[i]后面的三个位置作为结尾的合法子序列的方案数(显然这三个位置的字符相同)。

到了一个新的i,那么sum就必须全部重新维护(因为a[i]变了),

而sum[j][k]必然包含了从第二个串选1,2,...j-1的,第三个串选1,2,...,k-1位置的,按照二维前缀和的思想,sum[j][k]=sum[j-1][k]+sum[j][k-1]-sum[j-1][k-1]+val

考虑val是什么

就是以所有(<i,j-1,k-1)结尾的且能拼到i后面的子序列的方案数(要保证三个位置字符相同)

设这个东西为dp[j][k](i维省了),那么dp[j][k]可以在之前几轮i的时候,枚举到a[i]==b[j]==c[k]的(i,j,k)的时候更新到

只要我们先更新sum,再求i层的贡献,再更新dp[j][k]就行了。

代码(from:wxh)

#include <bits/stdc++.h>

using namespace std;

const int N = 234;
const int md = 1e9 + 7;

int n, a[N], dp[N][N], sum[N][N];
char board[N][N];

inline void add(int &x, int y) {
  x += y;
  if (x >= md) {
    x -= md;
  }
}

inline void sub(int &x, int y) {
  x -= y;
  if (x < 0) {
    x += md;
  }
}

int main() {
  int tt;
  scanf("%d", &tt);
  while (tt--) {
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
      scanf("%d", &a[i]);
      --a[i];
    }
    for (int i = 0; i < n; ++i) {
      scanf("%s", board[i]);
    }
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < n; ++j) {
        dp[i][j] = 0;
      }
    }
    int answer = 0;
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < n; ++j) {
        for (int k = 0; k < n; ++k) {
          sum[j + 1][k + 1] = board[a[j]][a[i]] == '1' ? dp[j][k] : 0;
          add(sum[j + 1][k + 1], sum[j + 1][k]);
          add(sum[j + 1][k + 1], sum[j][k + 1]);
          sub(sum[j + 1][k + 1], sum[j][k]);
        }
      }
      for (int j = 0; j < n; ++j) {
        for (int k = 0; k < n; ++k) {
          if (a[i] == a[j] && a[i] == a[k]) {
            int ways = 1;
            add(ways, sum[j][k]);
            add(answer, ways);
            add(dp[j][k], ways);
          }
        }
      }
    }
    printf("%d\n", answer);
  }
  return 0;
}
View Code

总结:

1.组合意义拆分次方

2.注意固定i以后,得到更多的条件以简化复杂度,比如这里sum就额外赋予了新的要求:子序列能接在i的后面。这个sum用平行的循环更新就行。

3.思路:组合意义转化->sum设出来->再维护dp->处理好先后顺序

 

C - Vasya and Robot

https://vjudge.net/contest/429333#problem/C

思路别想太复杂=。=

考虑选出一个区间行不行,发现只要考虑区间外面U,D,L,R个数就行了

然后发现可以二分。没了。

(其实有单调性可以只扫一遍,固定右端点,左端点往左走,直到成为合法区间,再往左移动右端点,左端点也不用回撤)

D - Berland Fair

 https://vjudge.net/contest/429333#problem/D

可以直接用线段树模拟。

注意,pushdown的标记也要下放给子区间!!!

 

pym的方法:

重复:

1.能完整绕圈就绕圈。(取模)

2.否则,从1出发暴力走一圈,模拟这一圈的过程。绕完以后,删除a[i]>T的i(用个堆删),维护剩下a[i]的和(便于1操作绕圈)

直到T变为0或者删完

复杂度:

每次绕圈会让T至少减半。

否则如果不能绕圈,那么暴力走一遍,如果走了一遍没有使T减半,那么意味着真正做出贡献的a[i]的和(设为S)很小,而其他没有做出贡献的a[i]就再也不能走了。

这样的话,S<<T,那么下次一定可以绕圈。所以还是能让T减半

很好写。

算是一种绕圈取模题的思路,考虑让规模减半,使得暴力次数不多。

posted @ 2021-03-22 15:59  *Miracle*  阅读(182)  评论(0编辑  收藏  举报