HDU - 7009 树上游走(树的直径+容斥)
題目鏈接:點擊查看
題目大意:給一棵樹,稱一個點集 S 是好的當且僅當存在一個點,其到 S 中所有點的距離互不相同,求 |S| 的最大值和使得 |S| 最大的 S 的個數
題目分析:不難看出 ∣S∣|S|∣S∣ 就是樹的直徑,而滿足條件的 SSS 的個數,無非就是枚舉每個點作為樹的根節點,然后將 nnn 個節點按層劃分,每一層恰好選擇一個點的方案數,這個可以根據乘法原理,將每一層節點的個數累乘起來即可。這樣確實可以計算出所有的答案,但是會有重復。
不過通過思考后可以看出,重復的部分當且僅當 SSS 中的點是一條鏈的時候,又因為 ∣S∣|S|∣S∣ 是樹的直徑,所以這條鏈也對應著樹上的一條直徑
維護答案的同時,順便維護一下樹的直徑有多少條即可
2021.8.4 UPDATE:
感謝vjudge巨巨的hack樣例:
之前也是感覺這個題重復的部分不只是直徑那么簡單,但是看到題解和AC代碼都是這個思路就不了了之了,補多校的題補的也沒時間去糾結這些小問題了。但是這位巨巨的樣例體現出了,重復的部分不一定是連續的一條鏈,所以我們該如何去正確的容斥呢?
首先統計貢獻的部分是沒有問題的,就是枚舉每個節點作為根節點,將所有節點按照深度分類,然后累乘起來,下面重點講一下如何容斥去重。
假設我們目前枚舉到了以點 xxx 為根節點的樹,先前已經處理過了根節點為 yyy 的樹,保證點 xxx 和點 yyy 都是樹的直徑的端點。當我們將這個形態的樹,都按照深度“拉直”后,設兩點之間的深度差為 deltadeltadelta 不難發現深度位于 delta+1delta+1delta+1 及之后的位置,兩棵樹相應位置的形態是完全一樣的,也就是說在這里會被重復統計,所以我們對于 cntcntcnt 維護一個后綴乘積,減去相應的答案即可
相比于之前的代碼多了點常數,懶得卡常了,留一份TLE的代碼
代碼:
// #pragma GCC optimize(2) // #pragma GCC optimize("Ofast","inline","-ffast-math") // #pragma GCC target("avx,sse2,sse3,sse4,mmx") #include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cmath> #include<cstring> #include<algorithm> #include<stack> #include<climits> #include<queue> #include<map> #include<set> #include<sstream> #include<cassert> #include<bitset> #include<list> #include<unordered_map> #define lowbit(x) (x&-x) using namespace std; typedef long long LL; typedef unsigned long long ull; template<typename T> inline void read(T &x) {T f=1;x=0;char ch=getchar();while(0==isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}while(0!=isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();x*=f; } template<typename T> inline void write(T x) {if(x<0){x=~(x-1);putchar('-');}if(x>9)write(x/10);putchar(x%10+'0'); } const int inf=0x3f3f3f3f; const int N=1e6+100; const int mod=998244353; vector<int>node[N]; int cnt[N],deep[N],max_deep; bool vis[N]; void dfs(int u,int fa,int dep) {cnt[dep]++;deep[u]=dep;max_deep=max(max_deep,dep);for(auto v:node[u]) {if(v==fa) {continue;}dfs(v,u,dep+1);} } int main() { #ifndef ONLINE_JUDGE // freopen("data.in.txt","r",stdin); // freopen("data.out.txt","w",stdout); #endif // ios::sync_with_stdio(false);int w;cin>>w;while(w--) {int n;read(n);for(int i=1;i<=n;i++) {node[i].clear();vis[i]=false;}for(int i=1;i<n;i++) {int u,v;read(u),read(v);node[u].push_back(v);node[v].push_back(u);}int mmax=-1;for(int rt=1;rt<=n;rt++) {dfs(rt,-1,1);}mmax=max_deep;for(int rt=1;rt<=n;rt++) {max_deep=0;dfs(rt,-1,1);if(max_deep==mmax) {vis[rt]=true;}}LL ans=0;for(int rt=1;rt<=n;rt++) {if(!vis[rt]) {continue;}memset(cnt,0,sizeof(int)*(n+5));dfs(rt,-1,1);cnt[mmax+1]=1;for(int i=mmax;i>=1;i--) {cnt[i]=(1LL*cnt[i]*cnt[i+1])%mod;}ans=(ans+cnt[1])%mod;for(int x=1;x<rt;x++) {if(vis[x]) {ans=(ans+mod-cnt[deep[x]+1])%mod;}}}cout<<mmax<<' '<<ans<<endl;}return 0; }總結
以上是生活随笔為你收集整理的HDU - 7009 树上游走(树的直径+容斥)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷 - P3246 [HNOI2016
- 下一篇: HDU - 7008 水题(打表)