1. 程式人生 > >【BestCoder Round 65D】【樹形DP 容斥思想】ZYB's Tree 求距離每個節點距離不超過k的節點數

【BestCoder Round 65D】【樹形DP 容斥思想】ZYB's Tree 求距離每個節點距離不超過k的節點數

ZYB's Tree

Accepts: 77 Submissions: 513 Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others) 問題描述
ZYBZYB有一顆NN個節點的樹,現在他希望你對於每一個點,求出離每個點距離不超過KK的點的個數.

兩個點(x,y)(x,y)在樹上的距離定義為兩個點樹上最短路徑經過的邊數,

為了節約讀入和輸出的時間,我們採用如下方式進行讀入輸出:

讀入:讀入兩個數A,BA,B,令fa_ifai為節點ii的父親,fa_1=0fa1=0;fa_i=(A*i+B)\%(i-1)+1
fai=(Ai+B)%(i1)+1
i \in [2,N]i[2,N] . 輸出:輸出時只需輸出NN個點的答案的xorxor和即可。
輸入描述
第一行一個整數TT表示資料組數。

接下來每組資料:

 一行四個正整數N,K,A,BN,K,A,B.

 最終資料中只有兩組N \geq 100000N1000001 \leq T \leq 51T5,1 \leq N \leq 5000001N500000,1 \leq K \leq 101K10,1 \leq A,B \leq 10000001A,B1000000
輸出描述
TT行每行一個整數表示答案.
輸入樣例
1
3 1 1 1
輸出樣例
3


#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;}
template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;}
const int N=5e5+10,M=1e6+10,Z=1e9+7,ms63=1061109567;
int casenum,casei;
int n,K,A,B;
int first[N],id;
int w[M],nxt[M];
bool e[N];
int f[N][12],ans;
inline void ins(int x,int y)
{
	++id;
	w[id]=y;
	nxt[id]=first[x];
	first[x]=id;
}
void dfs(int x)
{
	e[x]=1;
	f[x][0]=1;for(int i=1;i<=K;++i)f[x][i]=0;
	for(int z=first[x];z;z=nxt[z])
	{	
		int y=w[z];
		if(e[y])continue;
		dfs(y);
		for(int i=1;i<=K;++i)f[x][i]+=f[y][i-1];
	}
}
void dp(int x)
{
	e[x]=0;
	for(int z=first[x];z;z=nxt[z])
	{
		int y=w[z];
		if(!e[y])continue;
		for(int i=K;i>=2;--i)f[y][i]+=f[x][i-1]-f[y][i-2];++f[y][1];
		dp(y);
	}
	int tmp=0;
	for(int i=0;i<=K;++i)tmp+=f[x][i];
	ans^=tmp;
}
int main()
{
	scanf("%d",&casenum);
	for(casei=1;casei<=casenum;++casei)
	{
		scanf("%d%d%d%d",&n,&K,&A,&B);
		memset(first,0,n+2<<2);id=0;
		for(int i=2;i<=n;++i)
		{
			int j=((LL)A*i+B)%(i-1)+1;
			ins(i,j);ins(j,i);
		}
		ans=0;
		dfs(1);
		dp(1);
		printf("%d\n",ans);
	}
	return 0;
}
/*
【trick&&吐槽】
1,csy向我透露說,這次BC有題可以暴力過!
於是我就寫了個暴力,然後TLE……
果然還是要自己思考,不能再被騙了2333

2,A和B都是1e6範圍的數,所以乘法可能會爆int,一定要注意啊>_<

3,這麼水的題比賽時候竟然沒認真想過,我好蠢!

【題意】
給你一棵樹,樹上有n(5e5)個節點,讓你求出,對於所有點而言的,距離不超過K(1<=K<=10)的節點數。
然後輸出這所有節點數的異或和。

【型別】
樹形DP

【分析】
首先,這是樹結構。
然後,我們嘗試簡化問題。
如果求的,不是對於一個節點,所有距離在[1,K]的節點數,而是限制在子樹內的距離在[1,K]的節點數。
那麼,這道題,我們直接一個dfs就可以搞定。
就是從葉子節點開始,距離這個節點距離為[1,K]的節點數。
然後f[x][i]=∑f[son][i-1],i∈[1,K]
    f[x][0]=1

然而,我們還要求與非子樹內的節點,怎麼辦呢?
我們做完之前的預處理之後,只需要從父節點尋求轉移即可。

我們假設,我們已經知道了距離父節點x距離為0~K的所有點的點數。我們現在要向子節點y轉移。
顯然,f[y][i]+=f[x][i-1]-f[y][i-2],2<=i<=K。
	  ++f[y][1];
意思是,距離父節點x為i-1的節點,轉移到節點y的時候,距離就變成了i。
然而, 並非所有的節點都能做轉移。y子樹內的,距離為y為i-2的節點,距離父節點的距離也是i-1,但是距離y的距離並非為i。
所以我們把這些節點剔除。

一個轉移從子節點轉移而來,另外一個轉移從父節點轉移而來。
同時DP的時候要注意使用合理的拓撲序,就可以順利AC這道題啦。啦啦啦啦~

【時間複雜度&&優化】
O(nk)

【資料】
除錯程式碼——
for(int i=1;i<n;++i)
{
	int x,y;
	scanf("%d%d",&x,&y);
	ins(x,y);
	ins(y,x);
}
input
14 5 1 1
1 2
2 3
3 4
4 5
1 6
6 7
7 8
8 9
9 10
1 11
11 12
12 13
13 14
output
8
*/