CSUSTOJ 4000-你真的会数据结构吗?(状压+素数分解)

题目链接:http://acm.csust.edu.cn/problem/4000
博客园食用链接:https://www.cnblogs.com/lonely-wind-/p/13509092.html

Description

现在给你一棵有n个结点的有根树,根为结点1,每个结点有点权 a i a_i ai

现有q次询问:

t y p e   1 : 1   u   x type\ 1: 1\ u\ x type 1:1 u x表示将uu结点的点权修改为x;

t y p e   2 :   2   u type\ 2:\ 2\ u type 2: 2 u表示询问从结点uu的儿子结点中挑选出尽可能多的儿子结点(至少要选一个)使得 f ( n ) f(n) f(n)最小,其中n为所挑选出的结点的权值之积,函数 f ( n ) f(n) f(n)为d的个数,其中d满足 d ∣ n d\mid n dn(即d整除n)且 g c d ( d , n d ) = 1 gcd(d,\frac{n}{d})=1 gcd(d,dn)=1。注意若u为叶子结点则输出0 0。

Input
第一行包含一个正整数 n ( 5 ≤ n ≤ 1 e 5 ) n(5\leq n\leq 1e5) n(5n1e5),表示结点个数。

接下来 n − 1 n-1 n1行每行两个正整数u v,表示u和v之间有一条边,注意u不一定是v的父亲结点。

n + 1 n+1 n+1行包含n个正整数 a i ( 1 ≤ a i ≤ 30 ) a_i(1\leq a_i\leq 30) ai(1ai30),表示结点ii的权值。

n + 2 n+2 n+2行包含一个正整数 q ( 1 ≤ q ≤ 1 e 5 ) q(1\leq q\leq 1e5) q(1q1e5),表示询问次数。

接下来q行,每行第一个数为 o p ( o p = 1   o r   2 ) op(op=1\ or\ 2) op(op=1 or 2)

o p = 1 op=1 op=1,则该行还有两个正整数 u   x ( 1 ≤ u ≤ n , 1 ≤ x ≤ 30 ) u\ x(1\leq u\leq n,1\leq x\leq 30) u x(1un,1x30));

o p = 2 op=2 op=2,则该行还有一个正整数 u ( 1 ≤ u ≤ n ) u(1\leq u\leq n) u(1un)

Output
对于每一个 t y p e 2 type2 type2输出挑选的子结点个数及f(n)f(n)的值,用空格隔开,数据保证至少有一次type2。

Sample Input 1
7
1 2
1 3
2 4
2 5
3 6
3 7
2 3 4 5 6 7 8
3
2 2
1 2 1
2 1

Sample Output 1
1 2
1 1

此题用线段树来写的话稍微有点恶心心。。。。我们可以先想想暴力的方法。

我们先观察它要求什么,对于一个数,它一定可以分解为 s = p 1 k 1 × p 2 k 2 × ⋯ × p n k n s=p_1^{k_1}\times p_2^{k_2}\times \cdots \times p_n^{k_n} s=p1k1×p2k2××pnkn,那么如果要选一个数使得d,使得d是s的因子,而且 g c d ( d , n d ) = 1 gcd(d,\frac{n}{d})=1 gcd(d,dn)=1,那么只能选择 p i k i p_i^{k_i} piki,也就是说我们只需要求出n有几个素因子就好了,然后我们直接枚举是否选择该素因子,那么也就会得到 2 i 2^i 2i个d,其中 i i i表示的是该数的素因子个数。

那么对于每一个数就可以用一个二进制状态来表示它的素因子状态,比如说 4 = 1 , 8 = 1 , 3 = 10 , 6 = 11 4=1,8=1,3=10,6=11 4=1,8=1,3=10,6=11,然后我们对每个点记录其儿子节点的每个状态的数量:

void dfs(int u,int fa)
{
	father[u]=fa;
	for (auto v:g[u]){
		if (v==fa) continue;
		dfs(v,u);
		vis[u][stk[a[v]]]++;//记录其儿子该状态数量
	}
}

接下来修改的时候我们直接对vis进行修改就好了,直接该点父节点的将原来的该儿子的状态-1,现状态+1:

vis[father[u]][stk[a[u]]]--;
vis[father[u]][stk[x]]++;
a[u]=x;

接下来就是询问操作了,由于状态的数量只有30个,所以我们可以直接对每一个状态进行查询其个数和其可以选择的d的个数,即:

for (int i=1; i<=30; i++) {
	if (vis[u][stk[i]]) {
		if (selct[stk[i]]<ans2) {
			ans2=selct[stk[i]];
			ans1=vis[u][stk[i]];
		}
		else if (selct[stk[i]]==ans2) ans1=max(ans1,vis[u][stk[i]]);
	}
}

以下是AC代码:

#include 
using namespace std;

const int mac=1e5+10;
const int inf=1e9+10;

int a[mac];
vector<int>g[mac];
int prim[50],selct[1<<11],father[mac];
int stk[50];
unordered_map<int,int>vis[mac];

void dfs(int u,int fa)
{
	father[u]=fa;
	for (auto v:g[u]){
		if (v==fa) continue;
		dfs(v,u);
		vis[u][stk[a[v]]]++;
	}
}

int main(int argc, char const *argv[])
{
	int n,q;
	scanf ("%d",&n);
	for (int i=1; i<n; i++){
		int u,v;
		scanf ("%d%d",&u,&v);
		g[u].push_back(v); g[v].push_back(u);
	}
	for (int i=1; i<=n; i++) scanf ("%d",&a[i]);
	
	int cnt=0;
	for (int i=2; i<=30; i++){
		int mk=0;
		for (int j=2; j<=sqrt(i); j++)
			if (i%j==0) {mk=1; break;}
		if (!mk) prim[cnt++]=i;
	}

	for (int i=0; i<=30; i++){
		int nb=0;
		for (int j=0; j<cnt; j++){
			if (i%prim[j]==0) nb++,stk[i]|=1<<j;
		}
		selct[stk[i]]=1<<nb;
	}

	dfs(1,0);
	scanf ("%d",&q);
	while (q--){
		int opt,u,x;
		scanf ("%d%d",&opt,&u);
		if (opt==1){
			scanf ("%d",&x);
			vis[father[u]][stk[a[u]]]--;
			vis[father[u]][stk[x]]++;
			a[u]=x;
		}
		else {
			if (u!=1 && g[u].size()==1) {printf("0 0\n"); continue;}
			int ans1=0,ans2=inf;
			for (int i=1; i<=30; i++){
				if (vis[u][stk[i]]){
					if (selct[stk[i]]<ans2){
						ans2=selct[stk[i]];
						ans1=vis[u][stk[i]];
					}
					else if (selct[stk[i]]==ans2) ans1=max(ans1,vis[u][stk[i]]);
				}
			}
			printf ("%d %d\n",ans1,ans2);
		}
	}
	return 0;
}

暴力写完之后也可以写下线段树的写法。。。只不过有点恶心,首先你需要跑一下bfs序来找到每个点的儿子节点的连续编号,然后将所有的数丢到线段树里面,然后就是和暴力一样地跑了。。。将每个儿子的每个状态转移到父节点,然后区间询问一下就好了

你可能感兴趣的:(#,基本数学定理运用,#,状压DP,CSUSTOJ,素数分解,状压)