文章目录
0x08 Picnic Planning k限制最小生成树
Picnic Planning
题意:
思路: 迪杰特斯拉 限制树 回溯找最大边 得到多个森林 限制k
#include<iostream>
#include<cstring>
#include<map>
#include<queue>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
int a[30][30];
int connect[30][30];
int vis[30];
int fa[30];
int INF;
ll sum=0;
int cnt,use;
map<string,int> name;
int find(int x){
return (x==fa[x]) ? x:fa[x]=find(fa[x]);
}
void kruskal(int l,int r)
{
priority_queue< pair<int,pair<int,int>> > q;
for(int i=l;i<=cnt;i++){
for(int j=l+1;j<=cnt;j++){
q.push(make_pair(-a[i][j],make_pair(i,j)));
}
}
int x,y,fx,fy;
int w;
while(!q.empty())
{
w=-q.top().first;
x=q.top().second.first;
y=q.top().second.second;
q.pop();
fx=find(x);
fy=find(y);
if(fx==fy)continue;
fa[fy]=fa[fx];
sum+=w;
connect[x][y]=connect[y][x]=1;
if(x==1 or y==1) use++;
}
return ;
}
pair<int,int> dfs_max(int u)
{
vis[u]=1;
pair<int,int> edge1=make_pair(0,0);
if(connect[1][u]==1){
return edge1=make_pair(1,u);
}
pair<int,int> edge2;
for(int v=2;v<=cnt;v++){
if(connect[u][v]==0 or vis[v] ) continue;
edge2=dfs_max(v);
if(edge2.first==0) continue;
if(a[u][v] > a[edge1.first][edge1.second]) edge1=make_pair(u,v);
if(a[edge2.first][edge2.second] > a[edge1.first][edge1.second]) edge1=edge2;
}
return edge1;
}
void release(){
pair<int,int> edge1,edge2;
edge1=make_pair(0,0);
int id=1;
for(int i=2;i<=cnt;i++){
if(connect[1][i]) continue;
memset(vis,0,sizeof(vis));
edge2=dfs_max(i);
if(a[edge2.first][edge2.second] - a[1][i] >a[edge1.first][edge1.second]){
edge1=edge2;
id=i;
}
}
// cout<<id<<endl;
sum=sum-a[edge1.first][edge1.second]+a[1][id];
connect[1][id]=connect[id][1]=1;
connect[edge1.first][edge1.second]=connect[edge1.second][edge1.first]=0;
}
void solve(){
name["Park"]=1;
cnt=1;
memset(connect,0,sizeof(connect));
memset(a,0x3f,sizeof(a));
INF=a[0][0];
a[0][0]=0;
rep(i,1,29)
{
a[i][i]=0;
connect[i][i]=1;
fa[i]=i;
}
int n;
cin>>n;
string s1,s2;
int u,v,w;
rep(i,1,n){
cin>>s1>>s2;
// scanf("%s %s",s1,s2);
scanf("%d",&w);
if(!name[s1]) name[s1]=++cnt;
if(!name[s2]) name[s2]=++cnt;
u=name[s1]; v=name[s2];
a[u][v]=min(a[u][v],w);
a[v][u]=a[u][v];
// connect[u][v]=connect[v][u]=1;
}
int k;
cin>>k;
// cout<<"case1"<<endl;
kruskal(2,cnt);
kruskal(1,cnt);
// cout<<sum<<endl;
// cout<<"case2"<<endl;
for(int i=use+1;i<=min(k,cnt);i++){
release();
}
cout<<"Total miles driven: "<<sum<<endl;
}
int main (){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
0x09 Desert King POJ - 2728 (01规划,prime)
Desert King
题意:n(n<=1000)个村庄处在不同的海拔,现需要连通n个村庄,但不同成环,连通的边有成本和收益。
输入给出了每个村庄的位置(x,y),以及海拔高度。
收益即是两个村庄的距离,成本即是高度差的绝对值。
问
∑
H
e
i
g
h
t
∑
d
i
s
\frac{\sum{Height}}{\sum{dis}}
∑dis∑Height最小?
思路: 二分答案, 朴素prim算法(这道题不用用优先队列能过,用会超时…没想明白留个坑)
∑
H
e
i
g
h
t
∑
d
i
s
<
=
k
\frac{\sum{Height}}{\sum{dis}}<= k
∑dis∑Height<=k
(
H
1
−
d
1
∗
k
)
+
(
H
2
−
d
2
∗
k
)
+
.
.
.
.
+
(
H
n
−
d
n
∗
k
)
<
=
0
(H_1-d_1*k)+(H_2-d_2*k)+....+(H_n-d_n*k)<=0
(H1−d1∗k)+(H2−d2∗k)+....+(Hn−dn∗k)<=0
这只要二分k即可, 将
(
H
i
−
d
i
∗
k
)
(H_i-d_i*k)
(Hi−di∗k) 看成两个村庄的权值,用prim算法得到最小结果和(sum)。
如果sum<=0 说明k可行 减小,否则不可行 增大
ACcode
#include<iostream>
#include<cstring>
#include<queue>
#include<cmath>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
const double eps=1e-5;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
struct zw
{
ll x,y;
ll h;
}a[1010];
double dis[1010][1010],H[1010][1010];
int vis[N];
double d[N];
int n;
double getdis(int i,int j){
return sqrt( (double)(a[i].x-a[j].x)*(a[i].x-a[j].x) + (double)((a[i].y-a[j].y)*(a[i].y-a[j].y)));
}
bool nice(double k){
priority_queue< pair<double , int> > q;
double value;
double sum=0;
rep(i,0,n){
d[i]=1e11;
vis[i]=0;
}
d[1]=0;
int x;
for(int i=1;i<n;i++){
x=0;
for(int j=1;j<=n;j++){
if(!vis[j] and ( x==0 or d[x] > d[j])) x=j;
}
vis[x]=1;
for(int y=1;y<=n;y++){
if(!vis[y]) d[y]=min(d[y],H[x][y] - k*dis[x][y]);
}
}
for(int i=1;i<=n;i++) sum+=d[i];
if(sum<=0) return true;
else return false;
}
void init(){
}
void solve(){
n=read();
while(n){
rep(i,1,n){
a[i].x=read();
a[i].y=read();
a[i].h=read();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=getdis(i,j);
H[i][j]=abs( (double)(a[i].h-a[j].h));
}
}
//二分
double l=0,r=1e11;
while(fabs(l-r)>eps){
double mid=(l+r)/2;
if(nice(mid) ){
// cout<<"true"<<endl;
r=mid;
}else{
// cout<<"false"<<endl;
l=mid;
}
}
printf("%.3f\n",r);
n=read();
}
return ;
}
int main (){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
// getchar();
// getchar();
return 0;
}
0x0A 黑暗城堡 LibreOJ - 10064 (Dijkstra 生成树的过程)
黑暗城堡
题意:给一无向图,要求
思路: 模拟Dijkstra ,对于他实现的过程要理解透彻。每次对于一个点vis[u]=1.
问是多少种不同这样的修建方案。
当u被vis了。就知道u最短距离。 v满足dis[u]=dis[v]+edge(u,v)到u的可能就多一种. 其中v为u的邻边的点。
代码如下:
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e6+10;
const ll P=2147483647; //(1<<31)-1
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll head[N],nex[N],var[N],value[N];
ll cnt;
void addedge(ll u,ll v,ll w){
var[++cnt]=v;
value[cnt]=w;
nex[cnt]=head[u];
head[u]=cnt;
}
ll dis[1010];
bool vis[1010];
pair<ll, int > p[1010];
ll num[1010];
void djk(ll n){
// 初始化
rep(i,1,n){
dis[i]=0x3f3f3f3f;
vis[i]=0;
}
priority_queue< pair<ll,int> > q;
q.push(make_pair(0,1));
dis[1]=0;
while(!q.empty()){
// cout<<"cnt"<<endl;
ll u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=nex[i]){
if(dis[u] == dis[var[i]] + value[i]) num[u]++;
}
for(int i=head[u];i;i=nex[i]){
ll v=var[i];
ll w=value[i];
if(vis[v]) continue;
if(dis[v] > dis[u] + w ){
dis[v]=dis[u]+w;
q.push(make_pair(-dis[v],v));
}
}
}
num[1]=1;
return ;
}
void solve(){
// cout<<P<<endl;
int n,m;
ll u,v,w;
n=read();
m=read();
rep(i,1,m){
u=read();
v=read();
w=read();
addedge(u,v,w);
addedge(v,u,w);
}
djk(n);
// for(int i=1;i<=n;i++) printf("i: %d,dis: %lld\n",i,dis[i]);
// rep(i,1,n) cout<<num[i]<<" ";
ll sum=1;
rep(i,1,n){
sum=sum*num[i]%P;
}
cout<<sum<<endl;
return ;
}
int main (){
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
// system("pause");
getchar();
getchar();
return 0;
}
0x0B patrol 巡逻 黑暗爆炸 - 1912 (树的直径)
patrol 巡逻 黑暗爆炸 - 1912
ACwing巡逻
在写这道题之前。
蒟蒻补充一个知识点----树的直径
来源:《算法进阶指南》
树中最远两个点的距离称为树的直径
直径求法:两种方法都是o(n)
1.树形DP
可以适用在权值为正值和负值的树。
int vis[N];
ll D[N], maxdis;
void dfs(int x){
vis[x]=1;
for(int i=head[x];i;i=nex[i]){
int y=var[i];
if(vis[y]) continue;
dfs(y);
maxdis=max(maxdis,D[x]+D[y]+value[i]);
D[x]=max(D[x],D[y]+value[i]);
}
return ;
}
2.两次BFS实现
特点:能得到直径的距离经过的节点。 实现容易
int dis[N],pre[N];
int bfs(int u)
{
rep(i,1,n) dis[i]=0x3f3f3f3f;
queue<int> q;
q.push(u);
dis[u]=0;
pre[u]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=var[i];
if(dis[y]==0x3f3f3f3f){
dis[y]=dis[x]+value[i];
pre[y]=i;
q.push(y);
}
}
}
int id=1;
rep(i,2,n){
if(dis[i] > dis[id] ) id=i;
}
return id;
}
题意:
有多个村庄和一警察局编号1,他们道路连接是树形结构,每条道路距离都是1,警察每天要到达各个村庄,然后回局,可以在树中只能添加一条或两条。问从出发到回局能最短距离是多少?
思路:
BFS 得到一条最远距离,将该路径距离置为-1,maxdis1
然后用DP求添加第二条路,的最远距离。 maxdis2
结果:2*(n-1)-maxdis1+1-maxdis2+1
ACcode
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll head[N],nex[N],var[N],value[N];
ll pre[N]; // 存放第几条边
ll dis[N];
ll D[N];
int vis[N];
ll cnt=1;
int n,k;
void addedge(int u,int v,ll w){
var[++cnt]=v;
value[cnt]=w;
nex[cnt]=head[u];
head[u]=cnt;
}
int bfs(int u)
{
rep(i,1,n) dis[i]=0x3f3f3f3f;
queue<int> q;
q.push(u);
dis[u]=0;
pre[u]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=var[i];
if(dis[y]==0x3f3f3f3f){
dis[y]=dis[x]+value[i];
pre[y]=i;
q.push(y);
}
}
}
int id=1;
rep(i,2,n){
if(dis[i] > dis[id] ) id=i;
}
return id;
}
ll maxdis2;
void dfs(int x){
vis[x]=1;
for(int i=head[x];i;i=nex[i]){
int y=var[i];
if(vis[y]) continue;
dfs(y);
maxdis2=max(maxdis2,D[x]+D[y]+value[i]);
D[x]=max(D[x],D[y]+value[i]);
}
}
void solve(){
n=read();
k=read();
ll u,v;
rep(i,1,n-1){
u=read();
v=read();
addedge(u,v,1);
addedge(v,u,1);
}
// 两次dfs 得出树的最长路径,并且标记-1
int p=bfs(1);
int q=bfs(p);
ll maxdis1=dis[q];
for(;pre[q];q=var[pre[q]^1]) value[pre[q]] = value[pre[q]^1] = -1;
ll sum=2*(n-1);
sum=sum-maxdis1+1;
if(k==2){
dfs(1);
sum=sum-maxdis2+1;
}
printf("%lld\n",sum);
return ;
}
int main (){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
0x0C Core树网的核 黑暗爆炸 - 1999 (树的直径+最小偏心距)
传送门
题意:在一个无环连通图(树)中,在树直径上选取一小段路径距离<=s。这段路径两端是节点,也可两端是同一个节点。
问:选取一段在直径上的路径,问该路径到树上各个点的最小偏心距是多少。
思路:
1.所有的直径必定相交,而且各直径的终点汇聚于同一处
2.在任意一条直径上求出的最小偏心距都相等。
然后节点p到节点q距离需要满足<=s.
每一次二分,对p-q路径所有节点dfs,得到一个最大距离mx。
如果mx > bitsum, 那就false,应该最选取比bitsum大的。
代码构造:
用前向性建图,
一组反向边 编号 i 和(i^1) (cnt起始1)
pre[节点]=编号
得到一条直径上所有节点和距离。
然后二分细节: l<=r r=mid-1 l=mid+1
ACcode
// submitted by HNUST26
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e6+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll n,s;
ll head[N],nex[N],var[N],value[N];
ll cnt=1;
void addedge(ll u,ll v,ll w){
var[++cnt]=v;
value[cnt]=w;
nex[cnt]=head[u];
head[u]=cnt;
}
ll pre[N],vis[N];
ll dis[N];
ll sum=0;
vector<ll> path;
ll bfs(ll u){
rep(i,1,n) dis[i]=0x3f3f3f3f;
queue<ll> q;
q.push(u);
dis[u]=0;
pre[u]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
ll y=var[i];
if(dis[y] == 0x3f3f3f3f){
dis[y]=dis[x]+value[i];
pre[y]=i;
q.push(y);
}
}
}
int id=1;
rep(i,2,n){
if(dis[i] > dis[id]) id=i;
}
return id;
}
ll mx=0;
void dfs1(ll x,ll fa,ll sm){
mx=max(mx,sm);
for(int i=head[x];i;i=nex[i]){
if(var[i]==fa or vis[var[i]]) continue;
dfs1(var[i],x,sm+value[i]);
}
return ;
}
bool check(ll now){
ll i=0,j=0;
ll x,y;
for(int k=0;k<path.size();k++){
if(dis[path[k]] <=now) {
i=k;
x=path[k];
}
}
ll maxdis=dis[path[(int)path.size()-1]];
for(int k=(int)path.size()-1;k>=0;k--){
if(maxdis-dis[path[k]] <= now){
j=k;
y=path[k];
}
}
if(maxdis-dis[x]-(maxdis-dis[y]) >s ) return false;
for(int k=i;k<=j;k++) vis[path[k]]=1;
// cout<<path[k]<<" ";
// cout<<endl;
mx=0;
for(int k=i;k<=j;k++) dfs1(path[k],0,0);
for(int k=i;k<=j;k++) vis[path[k]]=0;
if(mx>now) return false;
else return true;
}
void solve(){
n=read();
s=read();
ll u,v,w;
rep(i,1,n-1){
u=read();
v=read();
w=read();
addedge(u,v,w);
addedge(v,u,w);
}
ll p,q;
p=bfs(1);
q=bfs(p);
sum=dis[q];
// cout<<sum<<endl;
// cout<<p<<" "<<q<<endl;
int q1=q;
ll ans=0;
for(;pre[q1];q1=var[pre[q1]^1]){
path.push_back(q1);
dis[q1]=ans;
ans+=value[pre[q1]];
}
path.push_back(p);
dis[p]=ans;
// for(int i=0;i<path.size();i++) cout<<path[i]<<" ";
// cout<<endl;
// for(int i=0;i<path.size();i++) cout<<dis[path[i]]<<" ";
ll l,r;
l=0;
r=2e9+10;
// r=26;
while(l<=r){
ll mid=(l+r)>>1;
if(check(mid)){
ans=mid;
r=mid-1;
// cout<<"mid:"<<mid<<endl;
}else{
l=mid+1;
}
}
// cout<<"endl"<<endl;
cout<<ans<<endl;
return ;
}
int main (){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}
LCA(最近公共祖先)
向上倍增 , 一次lCA 时间o(logn)
板子
ll head[N],nex[N],value[N],var[N];
int fa[N][25],d[N]; //深度
ll ans[N];
ll cnt=0;
void addedge(int u,int v,ll val)
{
var[++cnt] = v;
value[cnt] = val;
nex[cnt] = head[u];
head[u] = cnt;
}
void bfs(int u){
queue<int> q;
q.push(u);
d[u]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i])
{
int y=var[i];
if(d[y]) continue;
d[y] = d[x] +1;
fa[y][0] = x;
for(int i=1;i<=20;i++)
{
// y+2^i = y+2^(i-1) + 2^(i-1)
fa[y][i] = fa[fa[y][i-1]][i-1];
}
q.push(y);
}
}
}
int lca(int x,int y)
{
if(d[x] > d[y] ) swap(x,y);
for(int i=20;i>=0;i--){
if(d[fa[y][i]] >= d[x]){
y=fa[y][i];
}
}
if(x==y) return x;
for(int i=20;i>=0;i--){
if(fa[x][i] != fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
LCA 的tarjan算法 (最近公共祖先)
解决的问题给定一颗树,m组LCA查询,时间复杂度在o(n+m)
算法流程:
在下图的一颗树中, 问 (5,6),(1,5)的LCA是哪个?
在遍历这棵树 用深度搜索
vis 标记节点 0 未访问 1 访问了但没回溯 2访问了也回溯到了
从1节点开始,深搜到6节点,经过路径的节点vis标记1
之后回溯 到 2号节点,现在的vis标记是:1 1 0 2 1 2 (vis[1]=1,指1号节点访问了未回溯到)
然后访问到了 5。 问(5,6),(1,5)的LCA ?
因为6的vis是2。 (5,6)的LCA一定在5到根节点的路径上(1—>2—>5)
最近的是 2 ,所以LCA(5,6)=2;
这里2怎么做到在0(1)就能找出呢?用并查集 回溯到的组成一个集合即是(4,6)共用一个祖先2。
还不明白?没关系 ,看看后面代码~
板子
int fa[N];
int vis[N]; // 1访问 2范问且回溯 0 为访问
int get(int x)
{
return x==fa[x] ? x:fa[x]=get(fa[x]);
}
void tarjan(int x)
{
vis[x]=1;
for(int i=head[x];i;i=nex[i])
{
if(vis[var[i]]) continue;
tarjan(var[i]);
fa[var[i]] = x;
}
for(int i=0;i<query[x].size();i++)
{
int y=query[x][i];
if(vis[y]==2)
{
ans[query_id[x][i]] = get(y);
}
}
vis[x]=2;
return ;
}
0x0D network POJ3417 (tarjan在线lca+树上差分)
network
题意: 在无向连通图中分为主要边和附加边,N-1条主要边,任意两个节点存在只由主要边构成的路径。第一步切断主要边,第二步切断附加边,图被斩为两部分。现问:一共有多少种方案可以实现。
思路:分析知道主要边构成一颗树,附加边是非树边。现在就看看添加非树边,对形成的环的边+1,即是说明能覆盖了一次。后面以此。
对于边上覆盖0次的边,则第二步可以切断任意一条附加边。
这里提供了两种解法:
- 离线求解的附件边的 形成的环。 然后lca求得公共祖先,树上差分。 复杂度O(n+Mlogn)
ACcode:
#include<iostream>
#include<vector>
#include<queue>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=4e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
ll head[N],nex[N],value[N],var[N];
int fa[N][25],d[N]; //深度
ll ans[N];
ll cnt=0;
void addedge(int u,int v,ll val)
{
var[++cnt] = v;
value[cnt] = val;
nex[cnt] = head[u];
head[u] = cnt;
}
void bfs(int u){
queue<int> q;
q.push(u);
d[u]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i])
{
int y=var[i];
if(d[y]) continue;
d[y] = d[x] +1;
fa[y][0] = x;
for(int i=1;i<=20;i++)
{
// y+2^i = y+2^(i-1) + 2^(i-1)
fa[y][i] = fa[fa[y][i-1]][i-1];
}
q.push(y);
}
}
}
int lca(int x,int y)
{
if(d[x] > d[y] ) swap(x,y);
for(int i=20;i>=0;i--){
if(d[fa[y][i]] >= d[x]){
y=fa[y][i];
}
}
if(x==y) return x;
for(int i=20;i>=0;i--){
if(fa[x][i] != fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int dd[N],vs[N],vis[N];
void dfs(int u){
int w=dd[u];
vis[u]=1;
for(int i=head[u];i;i=nex[i]){
int v=var[i];
if(vis[v]) continue;
dfs(v);
w+=vs[v];
}
vs[u] = w;
return ;
}
void solve(){
int n,m;
n=read();
m=read();
int u,v;
rep(i,1,n-1){
u=read();
v=read();
addedge(u,v,0);
addedge(v,u,0);
}
bfs(1);
for(int i=1;i<=m;i++){
u=read();
v=read();
dd[u]++;
dd[v]++;
dd[lca(u,v)]-=2;
// cout<<lca(u,v)<<endl;
}
dfs(1);
ll sum=0;
for(int i=2;i<=n;i++){
if(vs[i]==0) sum+=m;
else if(vs[i]==1) sum++;
}
cout<<sum<<endl;
return ;
}
int main(){
solve();
getchar();
getchar();
return 0;
}
2.tarjan+树上差分
注意一点: 如果附加边 u==v 特判一下!!! (掉坑里过,de了一天的bug)
#include<iostream>
#include<vector>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
using namespace std;
int var[N],nex[N],head[N];
int cnt;
int vis[N];
int n,m;
ll sum=0;
int fa[N],d[N];
void addedge(int u,int v){
var[++cnt]=v;
nex[cnt] = head[u];
head[u] = cnt;
}
int head1[N],nex1[N],var1[N];
int cnt1;
void addquery(int u,int v){
var1[++cnt1] = v;
nex1[cnt1] = head1[u];
head1[u] = cnt1;
}
int get(int x){
return x==fa[x]? fa[x]:fa[x]=get(fa[x]);
}
void tarjan(int u){
vis[u] =1; fa[u] = u;
for(int i=head[u];i;i=nex[i]){
int v=var[i];
if(vis[v]) continue;
tarjan(v);
fa[v] = u;
}
for(int i=head1[u];i;i=nex1[i]){
int v=var1[i];
if(vis[v]==2){
// cout<<"lca"<<get(v)<<endl;
d[get(v)]-=2;
// d[u]++;
// d[v]++;
}
}
vis[u] = 2;
}
void dfs(int u,int dad){
for(int i=head[u];i;i=nex[i]){
int v=var[i];
if(v!=dad){
dfs(v,u);
d[u] += d[v];
}
}
if(u==1) return ;
if(d[u]==1) sum++;
else if(d[u]==0) sum+=m;
}
void solve(){
n=read();
m=read();
rep(i,1,n-1){
int u=read();
int v=read();
addedge(u,v);
addedge(v,u);
}
for(int i=1;i<=m;i++){
int u=read();
int v=read();
if(u==v) continue;
d[u]++;
d[v]++;
addquery(u,v);
addquery(v,u);
}
tarjan(1);
// for(int i=1;i<=n;i++) printf("i:%d d:%d\n",i,d[i]);
dfs(1,0);
cout<<sum<<endl;
return ;
}
int main (){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
solve();
getchar();
getchar();
return 0;
}