题意:

蛛网树是一颗平面树,满足点是该树的凸包的顶点上等价于其是叶子。

给定一个平面树,求有多少种对点集的划分,使得每个划分出来的集合都是蛛网树。

Solution

考虑树形 dp。设 \(f_u\) 是 \(u\) 子树内的划分方案。先考虑两种特殊情况,以 \(u\) 为最浅点的蛛网树只有 \(1,2\) 个点,是容易转移的(因为后面的凸包转移是按边所以这些不会统计到)。

先考虑一个凸包带来的贡献最后应当计算所有凸包带权的和累加到 f 上。设凸包为 \(S\),其带的权应为:

\[\prod_{u\not\in S,fa_u\in S}f_u
\]

考虑拆一下贡献。

把到叶子的线延长划分出若干区域。把每个区域内的贡献乘起来就可以了。

中间的那些点一定是在树上的路径(判断时有一些细节,不是半平面交起来!)。直接乘起来即可。这样,就解决了计算权值的问题。

还需要知道:哪些点对有贡献?即哪些点对可能成为相邻的叶子?显然,树上路径点必须在连边的一侧(不妨统一设为左侧),然后根据原树上边转移才能保证没有问题。具体来说,不是按照叶子转移,而是先对子树内的边标号,双向边两个方向不同,然后按照相邻叶子在原树上的起始边转移。

这里的转移类似于 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;
}

CF1326G 题解的更多相关文章

  1. 2016 华南师大ACM校赛 SCNUCPC 非官方题解

    我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...

  2. noip2016十连测题解

    以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...

  3. BZOJ-2561-最小生成树 题解(最小割)

    2561: 最小生成树(题解) Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1628  Solved: 786 传送门:http://www.lyd ...

  4. Codeforces Round #353 (Div. 2) ABCDE 题解 python

    Problems     # Name     A Infinite Sequence standard input/output 1 s, 256 MB    x3509 B Restoring P ...

  5. 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解

    题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...

  6. 2016ACM青岛区域赛题解

    A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Jav ...

  7. poj1399 hoj1037 Direct Visibility 题解 (宽搜)

    http://poj.org/problem?id=1399 http://acm.hit.edu.cn/hoj/problem/view?id=1037 题意: 在一个最多200*200的minec ...

  8. 网络流n题 题解

    学会了网络流,就经常闲的没事儿刷网络流--于是乎来一发题解. 1. COGS2093 花园的守护之神 题意:给定一个带权无向图,问至少删除多少条边才能使得s-t最短路的长度变长. 用Dijkstra或 ...

  9. CF100965C题解..

    求方程 \[ \begin{array}\\ \sum_{i=1}^n x_i & \equiv & a_1 \pmod{p} \\ \sum_{i=1}^n x_i^2 & ...

  10. JSOI2016R3 瞎BB题解

    题意请看absi大爷的blog http://absi2011.is-programmer.com/posts/200920.html http://absi2011.is-programmer.co ...

随机推荐

  1. java——棋牌类游戏五子棋(webwzq1.0)之一(基础)

    这是本人最近一段时间写的斗地主的java代码,大体框架都实现了,一些细节还需要后续优化. package basegame; import java.awt.Button; import java.a ...

  2. golang之操作kafka

    安装第三方包: go get github.com/IBM/sarama 生产者实例: package main import ( "fmt" "github.com/I ...

  3. Ubuntu18.04安装Java

    介绍 Java和JVM(Java的虚拟机)是许多软件所必需的,包括Tomcat,Jetty,Glassfish,Cassandra和Jenkins. 在本教程中,您将使用apt安装各种版本的Java ...

  4. msde2000的关于无法访问lonle实例的master数据库恢复

    某次关机重启后,lonele数据库实例无法访问,查看发现相应的服务(MSSQL$LONELE2.SQLAgent$LONELE2)无法启动. --------------------------- 服 ...

  5. 新型大语言模型的预训练与后训练范式,谷歌的Gemma 2语言模型

    前言:大型语言模型(LLMs)的发展历程可以说是非常长,从早期的GPT模型一路走到了今天这些复杂的.公开权重的大型语言模型.最初,LLM的训练过程只关注预训练,但后来逐步扩展到了包括预训练和后训练在内 ...

  6. vue3 中屏蔽控制台中的警告信息

    main.js中 const app = Vue.createApp({}); // 屏蔽错误信息 app.config.errorHandler = () => null; // 屏蔽警告信息 ...

  7. cv2, pil.image, plt.image 读图的差异

    人是习惯性动物,当我们第一次用opencv时,肯定会觉得opencv的imread()方式很奇怪,做图像出来天天说图像是RGB图RGB图,可opencv读出来的图,却是BGR的顺序.是不是很奇怪,还不 ...

  8. Redis应用—4.在库存里的应用

    大纲 1.库存模块设计 2.库存缓存分片和渐进式同步方案 3.基于缓存分片的下单库存扣减方案 4.商品库存设置流程与异步落库的实现 6.库存入库时"缓存分片写入 + 渐进式写入 + 写入失败 ...

  9. 使用 ASP.NET Core 5 Web API 创建可发现的 HTTP API

    使用 ASP.NET Core 5 Web API 创建可发现的 HTTP API https://devblogs.microsoft.com/aspnet/creating-discoverabl ...

  10. Shell_Shell 脚本中字符串的相关操作

    在我们的shell 编程中,一个必不可少的操作就是针对于字符串的操作, 重要有字符串替换,计算字符串长度 等等... 原文地址: https://blog.csdn.net/github_337369 ...