HDU4812-D Tree-树分治

题目描述

There is a skyscraping tree standing on the playground of Nanjing University of Science and Technology. On each branch of the tree is an integer (The tree can be treated as a connected graph with N vertices, while each branch can be treated as a vertex). Today the students under the tree are considering a problem: Can we find such a chain on the tree so that the multiplication of all integers on the chain (mod 10 6 + 3) equals to K?
Can you help them in solving this problem?
Input
There are several test cases, please process till EOF.
Each test case starts with a line containing two integers N(1 <= N <= 10 5) and K(0 <=K < 10 6 + 3). The following line contains n numbers v i(1 <= v i < 10 6 + 3), where vi indicates the integer on vertex i. Then follows N - 1 lines. Each line contains two integers x and y, representing an undirected edge between vertex x and vertex y.
Output
For each test case, print a single line containing two integers a and b (where a < b), representing the two endpoints of the chain. If multiply solutions exist, please print the lexicographically smallest one. In case no solution exists, print “No solution”(without quotes) instead.
For more information, please refer to the Sample Output below.
Sample Input
5 60
2 5 2 3 3
1 2
1 3
2 4
2 5
5 2
2 5 2 3 3
1 2
1 3
2 4
2 5
Sample Output
3 4
No solution
Hint
1. “please print the lexicographically smallest one.”是指: 先按照第一个数字的大小进行比较,若第一个数字大小相同,则按照第二个数字大小进行比较,依次类推。
2. 若出现栈溢出,推荐使用C++语言提交,并通过以下方式扩栈:
#pragma comment(linker,"/STACK:102400000,102400000")

题目分析

很清晰应该使用树分治,但是不同于一般树分治的是这里要求我们记录两个点的位置。这就意味这不能直接套之前的模板,而必须做出一些变通。

首先,求重心、进行分治应该是没有变化的,变的是对每个重心进行处理的部分。我们要把握住问题的难点在于如果记录答案的话,我们如何记录两个端点以及如何处理记录以后去掉子树中重复的操作

一般的树分治先将以重心为根的路径全部遍历一遍,将所有符合要求的答案保存,然后再在相同参数下遍历子树,将子树中满足条件的(说明刚才重复计算的部分)去掉。最后得到的就是答案。然而我们这里需要记录答案,朴素的想法就是同样将所有符合的都记录下来(同时记录两个端点),然后再在子树中将这些答案删除。可是这种删除将会非常耗费时间,我们必须在那个暂时保存答案的集合里面找到同一对数,最快也得O(nlogn),更何况我们这个操作要进行很多次。肯定会超时,所以我们必须换一个思路思考这个问题。

必须要删除的核心关键在于我们在第一次访问重心的时候没有办法判断是否在同一个子树上,所以只能先加上再减去。有没有方法能够避免计算同一个子树上的答案呢?

我原本的想法是每次访问重心得到其他点到重心的距离前先访问一次重心进行染色,将每个子树染成不同的颜色,然后再处理数据的时候再判断是否在同一个子树上来判断是否是有效数据。可是题目要求记录的是乘积等于k的字典序最小的一对端点。我们这样做就必须记录下所有的端点,答案可能很多,先不说存的下不,仅仅是记录的操作估计都会超时。

不得已我上网看了以下其他人是怎么做的。他们的做法很巧妙,也让我对树分治有了更深刻的理解。

首先,我们必须扭转将所有路径的答案都记录下来再进行处理的观念。这样对于只是求路径个数的可能没有什么问题,但是对于这种需要具体得到两个端点的情况会超时。所以我们必须可以在某一端点处直接判断是否存在有其他端点可以和它凑成答案。为了达到这个效果我们用一个数组T记录以当前重心为根的情况下各个端点距离重心距离x所对应的端点为T[x],因为最后需要的是字典序最小的,所以我们保存所有T[x]中最小的就可以。然后对于每一个端点我们得到它的距离Dis[i]以后判断是否有其他端点到树根的距离为x使得Dis[i]*x==k,为了快速得到这个x,我们需要预处理以下乘法逆元Rev[]。则和端点i对应的端点就应该是T[Rev[Dis[x]]]。如果i和对应的端点都存在则和当前答案比较,确定是否更新答案。

如果不清楚什么是乘法逆元可以看一下我的这篇博客:逆元

可是这算是解决了如何存储两个端点的问题。但是如何消除同一个子树中的端点的影响呢?我们的做法是不再像之前一样一下遍历所有的节点了,而是一个子树一个子树的遍历。并且对T数组的更新放在对答案的判断之后。这样做的原因是访问一个子树的时候,他们这个字数上的信息没有被更新到T数组中,因此T数组中保存的就是前面的子树的信息,也就不会出现访问到同一个子树的情况啦。

在每次开始分治,我们的T数组应该是互相没有联系的,因此需要进行清空

同时我们用一个栈来保存访问子树中所有节点的信息,先更新答案后再更新T数组。

还需要注意一点的是我们的信息是保存在节点上的,因此根节点的信息先不要传递,在更新答案的时候再算上就可以啦。

AC代码

#pragma comment(linker,"/STACK:102400000,102400000")
#include<iostream>
#include<cstring>
#include<cstdio>
#include<climits>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<cmath>#define RG register
#define IL inlineusing namespace std;
typedef long long ll;
const int MAXN=2e5+5;
const int MOD=1e6+3;
struct edge
{int to,last;
}Edge[MAXN<<2]; int Last[MAXN],tot;
int n,kk,SonNum[MAXN],MaxNum[MAXN],Vis[MAXN];
int root,rootx,dlen,ss,len;
ll Dis[MAXN],Rev[MOD+5],Num[MAXN];
int Stack[MAXN],T[MOD+5]; int top;
int ansl,ansr;IL int getint()
{int x=0,sign=1; char c=getchar();while(c<'0' || c>'9'){if(c=='-') sign=-1; c=getchar();}while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+c-'0'; c=getchar();}return x*sign;
}
IL ll getll()
{ll x=0,sign=1; char c=getchar();while(c<'0' || c>'9'){if(c=='-') sign=-1; c=getchar();}while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+c-'0'; c=getchar();}return x*sign;
}IL void Init()
{for(int i=0;i<=n;++i) Last[i]=0,Vis[i]=false;tot=0; ansl=ansr=INT_MAX;
}IL void AddEdge(int u,int v)
{Edge[++tot].to=v; Edge[tot].last=Last[u]; Last[u]=tot;
}IL void Read()
{for(RG int i=1;i<=n;++i) Num[i]=getll();int u,v;for(RG int i=1;i<n;i++){u=getint(); v=getint();AddEdge(u,v); AddEdge(v,u);}
}void GetRoot(int x,int father)
{int v;SonNum[x]=1; MaxNum[x]=1;for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(v==father || Vis[v]) continue;GetRoot(v,x);SonNum[x]+=SonNum[v];if(SonNum[v]>MaxNum[x]) MaxNum[x]=SonNum[x];}MaxNum[x]=max(MaxNum[x],ss-SonNum[x]);if(rootx>MaxNum[x]) root=x,rootx=MaxNum[x];
}void GetDis(int x,int father,ll now)
{ll t=now*Num[x]%MOD;Dis[x]=t; Stack[top++]=x;int v;for(RG int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(v==father|| Vis[v]) continue;GetDis(v,x,t);}
}void Update(ll x,ll y,int z)
{int tmp=T[Rev[x*y%MOD]*kk%MOD];if(!tmp) return;if(z>tmp) swap(z,tmp);if(z<ansl || z==ansl&&tmp<ansr) ansl=z,ansr=tmp;
}void Solve(int x)
{//memset(Dis,0,sizeof(Dis));int v;T[1]=x;Vis[x]=true;for(RG int i=Last[x];i;i=Edge[i].last){dlen=0; top=0;v=Edge[i].to; if(Vis[v]) continue;GetDis(v,x,1);for(RG int j=0;j<top;++j) Update(Num[x],Dis[Stack[j]],Stack[j]);for(RG int j=0;j<top;++j) if(T[Dis[Stack[j]]]==0 || T[Dis[Stack[j]]]>Stack[j]) T[Dis[Stack[j]]]=Stack[j];}for(int i=Last[x];i;i=Edge[i].last){dlen=0; top=0;v=Edge[i].to; if(Vis[v]) continue;GetDis(v,x,1);for(int j=0;j<top;++j) T[Dis[Stack[j]]]=0;}for(int i=Last[x];i;i=Edge[i].last){v=Edge[i].to; if(Vis[v]) continue;//ans-=Count(v,Deal(x,0));ss=SonNum[v]; rootx=INT_MAX; root=0;GetRoot(v,x);Solve(root);}
}IL void Work()
{rootx=INT_MAX; ss=n; root=0; GetRoot(1,0); Solve(root);
}IL void Write()
{if(ansl==INT_MAX && ansr==INT_MAX){printf("No solution\n");}else{printf("%d %d\n",ansl,ansr);}
}IL void Pre()
{Rev[1]=1;for(ll i=2;i<MOD;++i)Rev[i]=(MOD-MOD/i)*Rev[MOD%i]%MOD;
}int main()
{Pre();while(~scanf("%d%d",&n,&kk)){Init();Read();Work();Write();}return 0;
}

经验总结

对于一个问题首先要抽象出来需要用那种算法进行解决。局部的处理常常需要其他算法和数据结构的优化。尽可能不适用mapset等,能用数组还是尽量用数组。对数组的初始化也需要讲究技巧,不能简单使用一个memset,这种做法很容易超时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383742.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

成为C++高手之实战项目

https://blog.csdn.net/niu_gao/article/details/51458721 在内存中模拟出一副牌&#xff0c;然后模拟洗牌&#xff0c;发牌等动作。 流程是这样的&#xff1a;构建一副牌保存到一个数组中—洗牌—创建玩家—向玩家发牌–输出每个玩家的牌。 #include <stdio.h> #include…

C++中String类的实现

https://www.cnblogs.com/zhizhan/p/4876093.html原文&#xff1a;http://noalgo.info/382.html String是C中的重要类型&#xff0c;程序员在C面试中经常会遇到关于String的细节问题&#xff0c;甚至要求当场实现这个类。只是由于时间关系&#xff0c;可能只要求实现构造函数、…

Ubuntu软件更新失败

刚安装好Ubuntu以后需要将系统的软件都更新一下&#xff0c;但是遇到一个问题就是下载仓库信息失败&#xff0c;大概是这个样子的错误&#xff1a; 经国遇到这样的问题可以试一下下面这个命令&#xff1a; sudo rm -rf /var/lib/apt/lists/* sudo apt-get update参考网址&…

getsockname函数与getpeername函数的使用

https://www.tuicool.com/articles/V3Aveygetsockname和getpeername函数 getsockname函数用于获取与某个套接字关联的本地协议地址 getpeername函数用于获取与某个套接字关联的外地协议地址 定义如下&#xff1a;[cpp] view plaincopy#include<sys/socket.h> int gets…

Ubuntu根目录空间不足

自己在固态硬盘上安装的Ubuntu&#xff0c;结果只用了一天就显示磁盘空间不足。查看空间以后发现Ubuntu自己安装的时候默认给根目录分配的是10GB,然而我们下载的软件以及环境等一般都安装在根目录空间下&#xff0c;尤其是/usr目录所占的空间很大。 不得已我在网上查找了如何给…

Linux命令【一】基本命令

shell命令和bash命令相同&#xff0c;指的是命令解析器 快捷键 history 所有的历史命令ctrl P 向上滚动命令 ctrl N 向下滚动命令 ctrlB将光标向前移动 ctrlF将光标向后移动 ctrlA移动到命令行头部 ctrlE移动到命令行尾部 光标删除操作&#xff1a;删除光标前面字符ctrlh或…

The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement

http://www.jb51.net/article/119654.htmThe MySQL server is running with the --skip-grant-tables option so it cannot execute this statement 意思貌似MYSQL还运行在 --skip-grant-tables模式&#xff0c;如何让他回到原来的模式 第一种方法&#xff1a;原来在mysql.ini文…

解决Ubuntu“下载额外数据文件失败 ttf-mscorefonts-installer”的问题

参考博客&#xff1a;传送门 下载[ttf-mscorefonts-installer.zip](https://pan.baidu.com/s/1i5rLfMH) 密码: h76g 然后解压到下载的目录&#xff0c;在当前目录执行命令&#xff1a; sudo dpkg-reconfigure ttf-mscorefonts-installer这条命令手动指定文件夹的位置,重新配置…

【C语言】单链表的相关热点面试题(包括:从尾到头打印,逆置,冒泡,寻找中间节点,倒数k节点)

https://blog.csdn.net/hanjing_1995/article/details/51539599从尾到头打印单链表[cpp] view plaincopyvoid FromTailToHeadPrint(SListNode*& head) { stack<SListNode*> s; SListNode* cur head; while (cur) { s.push(cur); …

Linux命令【二】终端+Vim

需要先安装net-tools ifconfig eth0 网卡&#xff0c;硬件地址为MAC 地址&#xff0c;网卡编号&#xff0c;绝对不会重复 lo 回环地址 测试两台主机之间能否通信&#xff1a;ping IP或域名 [-c 4//回馈四条信息 -i//每隔多少秒回馈一次] 得到域名对应的IPnslookup 域名得到域…

Linux如何将文件中内容放到粘贴板上

没有找到如何在vim中将内容复制到粘贴板上&#xff0c;只找到了使用另一个软件进行操作。 首先安装xsel sudo apt-get install xsel # 将剪切板中的内容输出到文件 echo $(xsel --clipboard) >> a.txt# 将文件的内容复制到剪切板 cat a.txt | xsel --clipboard

【C语言】str类与men库函数的实现(如:strcpy,strcmp,strstr,strcat,memmove,memcpy)

https://blog.csdn.net/hanjing_1995/article/details/51539583strcpy拷贝源字符串到子字符串&#xff0c;包括‘\0’。代码实现&#xff1a;[cpp] view plaincopychar* strcpy(char* dst,const char* src) { assert(src); char* ret dst; while (*src) …

【笔试常考】C语言:深度剖析strlen,sizeof

https://blog.csdn.net/hanjing_1995/article/details/51539532在之前的博客中&#xff0c;我也探索过strlen,sizeof区别&#xff0c;详情可见博客http://10740184.blog.51cto.com/10730184/1705820。关于strlen,sizeof均可求字符串长度&#xff0c;这两者是笔试面试常考的知识…

vim环境配置 +vimplus配置

vim配置 参考网站&#xff1a;传送门 这个网站详细说明了vim配置的命令&#xff0c;我挑选了我想要用的部分&#xff0c;自己配置了一下。 配置vim的文件有两个&#xff0c;一个是/etc/vim/vimrc 这个是系统配置文件&#xff0c;修改这个文件将会修改所有用户的vim环境&…

剑指offer面试题:替换空格

https://blog.csdn.net/yanxiaolx/article/details/52235212题目&#xff1a;请实现一个函数&#xff0c;把字符串中的每个空格替换成“%20”。例如输入“We are happy.”&#xff0c;则输出“We%20are%20happy.”。解析&#xff1a;时间复杂度为O(n)的解法。完整代码及测试用例…

数据库原理及应用【一】引言

什么是数据库&#xff1a;一个大规模的集成的数据集合 作用&#xff1a;描述现实世界的实体(entities)以及实体之间的关系 管理数据库的系统软件&#xff1a;DBMS 文件是一个平滑的字符流&#xff0c;无法完成信息的检索和管理 数据&#xff08;data&#xff09;:用来描述现…

Linux命令【三】gcc编译+静态库+动态库+makefile+gdb调试

用C编译器编译源文件&#xff1a;gcc 源文件 -o 可执行文件名 详细步骤&#xff1a; gcc -E a.c -o a.i预处理器将头文件展开&#xff0c;宏替换&#xff0c;去掉注释gcc -S a.i -o a.s编译器将C文件变成汇编文件gcc -c a.s -o a.o汇编器将会变文件变成二进制文件gcc a.o -o a…

用c++模拟实现一个学生成绩管理系统

https://blog.csdn.net/yanxiaolx/article/details/53393437题目&#xff1a;用c模拟实现一个学生成绩的信息管理系统&#xff0c;要求能添加、删除、修改、查看和保存学生的信息等功能 源代码如下:[cpp] view plaincopy#define _CRT_SECURE_NO_WARNINGS #include<iostr…

Linux命令【四】文件+虚拟内存+常用系统函数

File*其实是一个结构体 文件描述符FD&#xff1a;索引到对应的磁盘文件文件读写位置指针FP_POS&#xff0c;如果同时读写需要注意文件指针的位置I/O缓冲区BUFFER&#xff1a;保存内存指针&#xff0c;默认大小是8kb&#xff0c;用于减小我们对硬盘操作的次数。因为我们对硬盘的…

Python3列表

操作&#xff1a;索引、切片、加、乘、检查成员、确定序列长度、确定最大最小元素 定义&#xff1a; 列表名 [元素]下标列表名[x] 截取:列表名[x:y] 更新&#xff1a; list[x]y 或者使用append()方法添加列表项删除&#xff1a; del list[x]常用操作&#xff1a; 截取与…