CF1326G 题解
题意:
蛛网树是一颗平面树,满足点是该树的凸包的顶点上等价于其是叶子。
给定一个平面树,求有多少种对点集的划分,使得每个划分出来的集合都是蛛网树。
Solution
考虑树形 dp。设 \(f_u\) 是 \(u\) 子树内的划分方案。先考虑两种特殊情况,以 \(u\) 为最浅点的蛛网树只有 \(1,2\) 个点,是容易转移的(因为后面的凸包转移是按边所以这些不会统计到)。
先考虑一个凸包带来的贡献最后应当计算所有凸包带权的和累加到 f 上。设凸包为 \(S\),其带的权应为:
考虑拆一下贡献。
把到叶子的线延长划分出若干区域。把每个区域内的贡献乘起来就可以了。
中间的那些点一定是在树上的路径(判断时有一些细节,不是半平面交起来!)。直接乘起来即可。这样,就解决了计算权值的问题。
还需要知道:哪些点对有贡献?即哪些点对可能成为相邻的叶子?显然,树上路径点必须在连边的一侧(不妨统一设为左侧),然后根据原树上边转移才能保证没有问题。具体来说,不是按照叶子转移,而是先对子树内的边标号,双向边两个方向不同,然后按照相邻叶子在原树上的起始边转移。
这里的转移类似于 P2924 的转移,先把向量排序,再枚举开始的边和按顺序枚举向量转移,叠加答案即可(注意取消掉只有自己的贡献)。
A 是排序后的向量集合。bh 是边的编号,P 向量的 d 是权值,F,S 是开始边和终止边。x,y 是枚举的起点边。这样的起点边当然必须在某个选定象限内。
for(int i=1;i<=tot;i++)gg[i]=0;
gg[bh[x][y]]=1;
for(auto P:A)(gg[P.F]+=gg[P.S]*P.d)%=mod;
(gg[bh[x][y]]+=mod-1)%=mod;
(f[u]+=gg[bh[x][y]])%=mod;
这样的转移显然是 \(O(n^3)\) 的。
为什么不能按点?可以看四号样例的图:
按照原理来讲,不应该有下面这个凸包:
但是按点的统计就不能避免这种情况。根本原因是,点转移不能区分一个点是否被真正当成叶子,像这个 A 点就连了两条边。容易发现按边就不会出任何问题。
最后还有一个问题:这个凸包必须存在一个相邻点对,满足他们在树上的路径包含当前处理 f 数组的 \(u\),实际上就是 \(u\) 在凸包叶子的虚树上。这个很容易保证,dp 时搞一下或者容斥一下均可。
代码在处理权值的时候参考了 std(因为之前一直写的是在半平面交内还能过 21 个点……)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=105,mod=998244353;
struct pt{int x,y;}p[maxn];
pt operator -(pt a,pt b){return {a.x-b.x,a.y-b.y};}
int operator ^(pt a,pt b){return a.x*b.y-a.y*b.x;}
int operator *(pt a,pt b){return a.x*b.x+a.y*b.y;}
int f[maxn],val[maxn][maxn];
#define VI vector<int>
VI lft[maxn][maxn],e[maxn],cur,Ares,E[maxn];
int n,T,dep[maxn];
void dfs(int u,int fa){
cur.push_back(u);
if(u==T)Ares=cur;
for(auto v:E[u]){
if(v==fa)continue;
dfs(v,u);
}
cur.pop_back();
}
VI getpath(int x,int y){
cur.clear();T=y;dfs(x,0);
return Ares;
}
bool check(pt a,pt b,pt c){
return ((b-a)^(c-a))>0;
}
bool good[maxn][maxn];
void dfs2(int u){
cur.push_back(u);
for(auto v:e[u])dfs2(v);
}
VI getsubt(int u){
cur.clear();
dfs2(u);
return cur;
}
int g[maxn];
struct vec{int u,v,d,S,F,ex;};
bool pd(pt a,pt b){
return 1-(b.y>a.y||(b.y==a.y&&b.x>=a.x));
}
bool pd(pt a){
return pd((pt){0,0},a);
}
bool operator <(vec a,vec b){
pt A=p[a.v]-p[a.u],B=p[b.v]-p[b.u];
if(pd(A)!=pd(B))return pd(A)<pd(B);
return (A^B)>0;
}
int Find(int u,const VI &A){
for(auto v:A)if(v==u)return 1;
return 0;
}
bool fat(int x,int y){
VI chain=getpath(x,y);
if(chain.size()==abs(dep[x]-dep[y])+1)return 1;
return 0;
}
bool pd2(pt a,pt b){
if((a^b)!=0)return (a^b)<0;
return (a*b)<0;
}
bool PD(pt a,pt b,pt c){
int x=pd2(a,b),y=pd2(a,c);
if(x!=y)return x<y;
return (b^c)>0;
}
bool dm[maxn][maxn],ckd[maxn][maxn];
vector<int> G[maxn];
int bh[maxn][maxn],gg[maxn*maxn];
void dp(int u){
int prod=1;
for(auto v:e[u]){
dp(v);
(prod*=f[v])%=mod;
}
f[u]=prod;
for(auto v:e[u]){
int p=1;
for(auto w:e[v])(p*=f[w])%=mod;
for(auto v2:e[u])if(v2!=v)(p*=f[v2])%=mod;
(f[u]+=p)%=mod;
}
VI sub=getsubt(u);
for(auto u:sub)G[u].clear();
int tot=0;
for(auto u:sub){
for(auto v:e[u]){
G[u].push_back(v);
G[v].push_back(u);
bh[u][v]=++tot;
bh[v][u]=++tot;
}
}
vector<vec> A;
for(auto x:sub){
for(auto y:sub){
if(x==y||!good[x][y]||dm[x][y])continue;
if(((p[y]-p[x])^(p[u]-p[x]))<0)continue;
VI path=getpath(x,y);int N=path.size()-1,d=1;
for(int i=0;i<=N;i++){
int v=path[i];
pt A,B;
if(i==0)A=p[v]-p[path[i+1]];
else A=p[path[i-1]]-p[v];
if(i==N)B=p[v]-p[path[i-1]];
else B=p[path[i+1]]-p[v];
for(auto w:e[v]){
if(!PD(A,p[w]-p[v],B))continue;
if((i>0&&w==path[i-1])||(i<N&&w==path[i+1]))continue;
(d*=f[w])%=mod;
}
}
bool tp=0;
if(Find(u,path))tp=1;
A.push_back({x,y,d,bh[path[0]][path[1]],bh[path[N]][path[N-1]],tp});
}
}
sort(A.begin(),A.end());
for(auto x:sub){
for(auto y:G[x]){
if(pd(p[x],p[y]))continue;
for(int i=1;i<=tot;i++)gg[i]=0;
gg[bh[x][y]]=1;
for(auto P:A)(gg[P.F]+=gg[P.S]*P.d)%=mod;
(gg[bh[x][y]]+=mod-1)%=mod;
(f[u]+=gg[bh[x][y]])%=mod;
}
}
for(auto x:sub){
for(auto y:G[x]){
if(pd(p[x],p[y]))continue;
for(int i=1;i<=tot;i++)gg[i]=0;
gg[bh[x][y]]=1;
for(auto P:A)if(P.ex==0)(gg[P.F]+=gg[P.S]*P.d)%=mod;
(gg[bh[x][y]]+=mod-1)%=mod;
(f[u]+=mod-gg[bh[x][y]])%=mod;
}
}
}
int fa[maxn];
void dfs1(int u,int f){
fa[u]=f;dep[u]=dep[f]+1;
for(auto v:e[u]){
if(v==f)continue;
dfs1(v,u);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>p[i].x>>p[i].y;
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
e[v].push_back(u);
e[u].push_back(v);
E[u].push_back(v);
E[v].push_back(u);
dm[u][v]=dm[v][u]=1;
}
dfs1(1,0);
for(int i=2;i<=n;i++){
VI A;
for(auto u:e[i])if(u!=fa[i])A.push_back(u);
swap(A,e[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j||dm[i][j])continue;
VI chain=getpath(i,j);
bool ck=1;
for(auto u:chain)
if(u!=i&&u!=j&&!check(p[i],p[j],p[u])){
ck=0;break;
}
good[i][j]=ck;
}
}
dp(1);
cout<<f[1]<<endl;
return 0;
}