2021“MINIEYE杯”中国大学生算法设计超级联赛 第四场题解
第一题傻瓜题卡了两小时?
题意:
给定一棵树,树上每个点的编号从1 - N,然后每个点有一个值。
现在要你从树上每一个点出发,到其他点,然后带入到该函数中求函数值......
有点绕,我们打个比方:
比如现在我们从第 i 个点出发,显然轮到遍历第 j 个点了,函数中 ai,j 代表的就是从i -> j我能拿去不同权值的数量
比如下面这样一棵树
其中:
1 -> 1
2 -> 1
3 -> 4
4 -> 5
5 -> 1
6 -> 4
以上是各个点的权值
那么我们从1 -> 2的ai,j就是1,因为只能拿到1这个种类的数
但是我们从1 -> 4的ai,j就是2,因为我们能拿到1和5这两个数
知道了这个ai,j是怎么回事,下一步就是怎么算?
由于是从一个点出发,我们需要遍历整个图,然后对于一棵树我们可以知道如下性质:
每两点之间的路径有且只有一条(如果不重复经过一条边的话)
那么其实我们从一个节点出发,遍历一遍这个树,就能得到ai,j的值了
具体DFS代码如下:
void dfs(int cur,int fa) { if(!cn[c[cur]]) now[cur] = now[fa] + 1; else now[cur] = now[fa]; cn[c[cur]] += 1; for(int i = head[cur];i;i = a[i].ne) { int to = a[i].to; if(to != fa) dfs(to,cur); } cn[c[cur]] -= 1; }
简单解释一下:
从cur点出发,如果当前点的权值是第一次遍历得到,那么就相当于是在父亲结点的a i , j基础上+1
否则就是父亲结点的值
然后记录一下这个权值。
后面我们退出当前节点时需要将此权值回溯,也就是 - 1操作~
完整代码:
#include <iostream> #include <cstring> #include <queue> using namespace std; const int MAXN = 2000 + 7; const int MOD1 = 1e9 + 7; const int MOD2 = 1e9 + 9; typedef long long ll; struct node { int ne,to; int w; }a[MAXN<<2]; int head[MAXN],cnt; void add(int x,int y,int w = 0) { a[++cnt].ne = head[x]; head[x] = cnt; a[cnt].to = y; a[cnt].w = w; } ll c[MAXN]; ll pw1[MAXN],pw2[MAXN]; void ini() { pw1[0] = pw2[0] = 1; for(int i = 1;i < MAXN;i++) { pw1[i] = pw1[i - 1]*19560929%MOD1; pw2[i] = pw2[i - 1]*19560929%MOD2; } } ll now[MAXN]; ll cn[MAXN]; void dfs(int cur,int fa) { if(!cn[c[cur]]) now[cur] = now[fa] + 1; else now[cur] = now[fa]; cn[c[cur]] += 1; for(int i = head[cur];i;i = a[i].ne) { int to = a[i].to; if(to != fa) dfs(to,cur); } cn[c[cur]] -= 1; } int main() { int t; ini(); // ll ans = 0; // int b[6] = {1,1,2,2,1,2}; // for(int i = 0;i < 6;i++) // { // ans = ans + b[i]*pw1[i]%MOD1; // ans %= MOD1; // } // cout<<ans; // return 0; scanf("%d",&t); while(t--) { for(int i = 1;i <= cnt;i++) head[i] = 0; cnt = 0; int n; scanf("%d",&n); for(int i = 2;i <= n;++i) { int x; scanf("%d",&x); add(x,i); add(i,x); } for(int i = 1;i <= n;++i) scanf("%lld",&c[i]); for(int i = 1;i <= n;++i) { now[i] = 0; dfs(i,i); ll ans1 = 0,ans2 = 0; for(int j = 1;j <= n;++j) { ans1 = ans1 + now[j]*pw1[j - 1]%MOD1; ans1 %= MOD1; ans2 = ans2 + now[j]*pw2[j - 1]%MOD2; ans2 %= MOD2; } printf("%lld %lld\n",ans1,ans2); } } return 0; }
两场连续的比赛都有从1,1出发到N,M,而且每次都是往右或者往下走的这种题,还是不会,真彩
题意:
给你一个僵尸,开始在(1,1),只能往右走和往下走,然后有些地方有雷,问你僵尸可以访问的节点数量
思路:
首先我们非常容易得到一个结论,那就是
第一行出现的第一个禁区之后的所有点我们都不能走到
嗯,然后就没有然后了
后面的情况会有些复杂,dalao们觉得简单当我没说QAQ
我们思考一下这样一个问题:
对于一行一段连续的区间,什么情况下我们才能走这段区间的点呢?
看下面的图~