BZOJ3583:杰杰的女性朋友 矩阵乘法+谜の卡常数

思路:

我们可以预处理出一个矩阵\(P\),其中\(P[i][j]\)表示从\(i\)到\(j\)的道路数目.

这样我们大概可以搞出从\(u\)到\(v\)正好是\(d\)条路径的方案数.如果是不大于\(d\)条路径,我们就可以加一个计数器,每次累加能够到\(v\)的方案数.

这样就是一个\((N+1)*(N+1)\)的转移矩阵.如果我们每次用这个转移矩阵乘的话就要爆掉了.

 

发现矩阵\(P\)的算法非常奇葩.我们可以将其分解成一个\((N+1)*(K+1)\)的矩阵\(out\)与一个\((K+1)*(N+1)\)的矩阵\(in\)的乘积.

于是\(P^d=(out*in)^d\).

我们发现\(P^d=(out*in)^d=out*(in*out)^{d-1}*in\).注意到\(in*out\)是一个\((K+1)*(K+1)\)的矩阵,算起来毫无压力.

我们的答案矩阵是一个\(1*(N+1)\)的矩阵,容易发现这些东西乘到一起也只是\(O(NK^2)\)级别的.

 

因此我们的复杂度就是\(O(Q(K^3logd+NK^2))\).

 

(注意本题卡常数)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
static const int mod=(1e9)+7;
inline void inc(int&x,int y){if((x+=y)>=mod)x-=mod;}
struct Matrix{
    int d[1010][1010],w,h;
    Matrix(){}
    Matrix(int _w,int _h):w(_w),h(_h){memset(d,0,sizeof d);}
    void output(){
        printf("w=%d h=%d\n",w,h);
        for(int i=1;i<=w;++i){
            for(int j=1;j<=h;++j)printf("%d ",d[i][j]);puts("");
        }
        puts("");
    }
};
long long calc=0;
Matrix t,res,ret;
Matrix mul(const Matrix&A,const Matrix&B){
    res.w=A.w,res.h=B.h;
    for(int i=1;i<=res.w;++i)for(int j=1;j<=res.h;++j){
        res.d[i][j]=0;for(int k=1;k<=A.h;++k)++calc,inc(res.d[i][j],(long long)A.d[i][k]*B.d[k][j]%mod);
    }
    return res;
}
Matrix Pow(const Matrix&x,int y){//for w=h
    t=ret=x;y--;for(;y;y>>=1,t=mul(t,t))if(y&1)ret=mul(ret,t);return ret;
}
#define N 1010
#define M 21
int n,m,in[N][M],out[N][M];
Matrix Solve1,Solve2,Solve3,ans;
int main(){
    #ifndef ONLINE_JUDGE
    freopen("tt.in","r",stdin);
    #endif
    int n,m;scanf("%d%d",&n,&m);
    register int i,j;
    for(i=1;i<=n;++i){for(j=1;j<=m;++j)scanf("%d",&out[i][j]);for(j=1;j<=m;++j)scanf("%d",&in[i][j]);}
    int q,u,v,d;
    scanf("%d",&q);
    while(q--){
        calc=0;//debug
        scanf("%d%d%d",&u,&v,&d);
        if(d==0)printf("%d\n",u==v?1:0);
        else if(d==1){
            int ret=0;for(int i=1;i<=m;++i)ret=(ret+(long long)out[u][i]*in[v][i]%mod)%mod;
            printf("%d\n",(ret+(u==v?1:0))%mod);
        }
        else{
            Solve1=Matrix(n+1,m+1),Solve2=Matrix(m+1,n+1);
            for(i=1;i<=n;++i)for(j=1;j<=m;++j)Solve1.d[i][j]=out[i][j];
            Solve1.d[n+1][m+1]=1;
            for(i=1;i<=n;++i)for(j=1;j<=m;++j)Solve2.d[j][i]=in[i][j];
            for(j=1;j<=m;++j)Solve2.d[j][n+1]=in[v][j];
            Solve2.d[m+1][n+1]=1;
            //Solve1.output();
            //Solve2.output();
            Solve3=mul(Solve2,Solve1);//Solve3.output();
            Solve3=Pow(Solve3,d-1);
            ans=Matrix(1,n+1);ans.d[1][u]=1;
            ans=mul(ans,Solve1),ans=mul(ans,Solve3),ans=mul(ans,Solve2);
            printf("%d\n",(ans.d[1][n+1]+(u==v?1:0))%mod);
        }
        //printf("%I64d\n",calc);
    }
    return 0;
}
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
static const int mod=(1e9)+7;
inline void inc(int&x,int y){if((x+=y)>=mod)x-=mod;}
#define N 1010
#define M 22
int n,m,in[N][M],out[N][M];
int Solve1[N][M],Solve2[M][N],Solve3[M][M],ans[N][N],reg[N][N],t[M][M],one[M][M];
inline void work(int d){//solve Solve3^d
    register int i,j,k;
    memset(one,0,sizeof one);for(i=1;i<=m+1;++i)one[i][i]=1;
    for(i=1;i<=m+1;++i)for(j=1;j<=m+1;++j)t[i][j]=Solve3[i][j];
    while(d){
        if(d&1){
            for(i=1;i<=m+1;++i)for(j=1;j<=m+1;++j){
                reg[i][j]=0;for(k=1;k<=m+1;++k)inc(reg[i][j],(long long)one[i][k]*t[k][j]%mod);
            }
            for(i=1;i<=m+1;++i)for(j=1;j<=m+1;++j)one[i][j]=reg[i][j];
        }
        d>>=1;
        for(i=1;i<=m+1;++i)for(j=1;j<=m+1;++j){
            reg[i][j]=0;for(k=1;k<=m+1;++k)inc(reg[i][j],(long long)t[i][k]*t[k][j]%mod);
        }
        for(i=1;i<=m+1;++i)for(j=1;j<=m+1;++j)t[i][j]=reg[i][j];
    }
    for(i=1;i<=m+1;++i)for(j=1;j<=m+1;++j)Solve3[i][j]=one[i][j];
}
void pr1(){
    puts("Solve1");
    for(int i=1;i<=n+1;++i){
        for(int j=1;j<=m+1;++j)printf("%9d ",Solve1[i][j]);puts("");
    }
}
void pr2(){
    puts("Solve2");
    for(int i=1;i<=m+1;++i){
        for(int j=1;j<=n+1;++j)printf("%9d ",Solve2[i][j]);puts("");
    }
}
void pr3(){
    puts("Solve3");
    for(int i=1;i<=m+1;++i){
        for(int j=1;j<=m+1;++j)printf("%9d ",Solve3[i][j]);puts("");
    }
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("tt.in","r",stdin);
    #endif
    scanf("%d%d",&n,&m);
    register int i,j,k;
    for(i=1;i<=n;++i){for(j=1;j<=m;++j)scanf("%d",&out[i][j]);for(j=1;j<=m;++j)scanf("%d",&in[i][j]);}
    int q,u,v,d;
    scanf("%d",&q);
    while(q--){
        scanf("%d%d%d",&u,&v,&d);
        if(d==0)printf("%d\n",u==v?1:0);
        else if(d==1){
            int ret=0;for(int i=1;i<=m;++i)ret=(ret+(long long)out[u][i]*in[v][i]%mod)%mod;
            printf("%d\n",(ret+(u==v?1:0))%mod);
        }
        else{
            memset(Solve1,0,sizeof Solve1);
            for(i=1;i<=n;++i)for(j=1;j<=m;++j)Solve1[i][j]=out[i][j];
            Solve1[n+1][m+1]=1;
            memset(Solve2,0,sizeof Solve2);
            for(i=1;i<=n;++i)for(j=1;j<=m;++j)Solve2[j][i]=in[i][j];
            for(j=1;j<=m;++j)Solve2[j][n+1]=in[v][j];
            Solve2[m+1][n+1]=1;
            
            for(i=1;i<=m+1;++i)for(j=1;j<=m+1;++j){
                Solve3[i][j]=0;for(k=1;k<=n+1;++k)inc(Solve3[i][j],(long long)Solve2[i][k]*Solve1[k][j]%mod);
            }
            
            work(d-1);
            
            for(j=1;j<=n+1;++j)ans[1][j]=0;ans[1][u]=1;
            
            for(i=1;i<=1;++i)for(j=1;j<=m+1;++j){
                reg[i][j]=0;for(k=1;k<=n+1;++k)inc(reg[i][j],(long long)ans[i][k]*Solve1[k][j]%mod);
            }for(i=1;i<=1;++i)for(j=1;j<=m+1;++j)ans[i][j]=reg[i][j];
            
            for(i=1;i<=1;++i)for(j=1;j<=m+1;++j){
                reg[i][j]=0;for(k=1;k<=m+1;++k)inc(reg[i][j],(long long)ans[i][k]*Solve3[k][j]%mod);
            }for(i=1;i<=1;++i)for(j=1;j<=m+1;++j)ans[i][j]=reg[i][j];
            
            for(i=1;i<=1;++i)for(j=1;j<=n+1;++j){
                reg[i][j]=0;for(k=1;k<=m+1;++k)inc(reg[i][j],(long long)ans[i][k]*Solve2[k][j]%mod);
            }for(i=1;i<=1;++i)for(j=1;j<=n+1;++j)ans[i][j]=reg[i][j];
            
            printf("%d\n",(ans[1][n+1]+(u==v?1:0))%mod);
        }
    }
    return 0;
}

BZOJ2276:[Poi2011]Temperature 单调性+线段树

思路:

貌似存在\(O(n)\)的算法,不过由于我太弱只想到了\(O(nlogn)\).

现在很困就发代码了...有问题回复找我吧.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define N 1000010
struct SegmentTree{
    int a[2100000],M;
    inline void Init(int _siz){
        for(M=1;M<(_siz+2);M<<=1);
        memset(a,0xc0,sizeof a);
    }
    inline void upd(int x,int to){
        for(a[x+=M]=to,x>>=1;x;x>>=1)a[x]=max(a[x<<1],a[x<<1^1]);
    }
    inline int ask(int tl,int tr){
        int res=-1<<30;
        for(tl+=M-1,tr+=M+1;tl^tr^1;tl>>=1,tr>>=1){
            if(~tl&1)res=max(res,a[tl^1]);
            if(tr&1)res=max(res,a[tr^1]);
        }return res;
    }
}Seg;
 
int up[N];
 
namespace Fio{
    inline int getc(){
#ifndef ONLINE_JUDGE
        static const int L=1<<1;
#else
        static const int L=1<<15;
#endif
        static char buf[L],*S=buf,*T=buf;
        if(S==T){T=(S=buf)+fread(buf,1,L,stdin);if(S==T)return EOF;}return*S++;
    }
    inline bool digit(char c){return c>='0'&&c<='9';}
    template<typename T>inline void Get(T&x){
        int c=0;while(!digit(c=getc())&&c!='-');bool sign=c=='-';x=sign?0:c-'0';
        while(digit(c=getc()))x=(x<<1)+(x<<3)+c-'0';if(sign)x=-x;
    }
}
 
int main(){
    int n;Fio::Get(n);register int i,j;
    Seg.Init(n);
    int x;for(i=1;i<=n;++i)Fio::Get(x),Fio::Get(up[i]),Seg.upd(i,x);
     
    int nowmax=Seg.ask(1,1);
    int lans,ans;
    for(lans=2;lans<=n;++lans){
        if(nowmax>up[lans])break;
        nowmax=max(nowmax,Seg.ask(lans,lans));
    }ans=--lans;
     
    for(i=2;i<=n;++i){
        nowmax=Seg.ask(i,lans);
        for(++lans;lans<=n;++lans){
            if(nowmax>up[lans])break;
            nowmax=max(nowmax,Seg.ask(lans,lans));
        }--lans;
        ans=max(ans,lans-i+1);
    }
     
    printf("%d",ans);
     
    return 0;
}

 

BZOJ1821:[JSOI2010]Group 部落划分 Group 二分+并查集

思路:

我保证这是一种奇葩算法.

我们二分答案,那么问题就转化为若任意两个部落之间的距离\(\leq{l}\),是否能够划分为为\(k\)组.

我们能发现以下性质:若能够划分为\(k'\)组,且满足\(k'>k\),则必定能够划分为\(k\)组.原因是我们可以合并一些块,那么部落之间距离的最小值必然不会减少.

因此我们只需考虑对于一个给定的\(l\),最多能够划分为几组.

考虑两个点,若两点距离\(\leq{l}\),那么必定划分在一组中.那么现在有若干个连通块,不同连通块之间的所有点的距离均\(>l\),显然当前连通块数就是我们要求的最大连通块数.

于是二分+并查集水.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
 
#define N 1010
int n,k,x[N],y[N],r[N];
 
inline void init(){for(int i=1;i<=n;++i)r[i]=i;}
inline int find(int x){int q=x,rq;for(;x!=r[x];x=r[x]);for(;q!=x;q=rq)rq=r[q],r[q]=x;return x;}
inline void merge(int x,int y){r[find(x)]=find(y);}
typedef double f2;
 
f2 dis(int a,int b){
    return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
 
 
int main(){
    scanf("%d%d",&n,&k);register int i,j;
    for(i=1;i<=n;++i)scanf("%d%d",&x[i],&y[i]);
     
    f2 Maxdis=0;
    for(i=1;i<=n;++i)for(j=i+1;j<=n;++j)Maxdis=max(Maxdis,dis(i,j));
     
    f2 L=0,R=Maxdis,mid;
    while(R-L>1e-4){
        mid=(L+R)*.5;
        init();
        for(i=1;i<=n;++i)for(j=i+1;j<=n;++j)if(dis(i,j)<=mid)merge(i,j);
         
        int cnt=0;for(i=1;i<=n;++i)cnt+=(i==find(i));
         
        if(cnt>=k)L=mid;else R=mid;
    }
     
    printf("%.2lf",L);
     
    return 0;
}

BZOJ2144:跳跳棋 LCA

思路:

对于一个状态\((x,y,z)(x<y<z)\),我们发现可行的操作仅有三种:

(1)将\(y\)向左移动

(2)将\(y\)向右移动

(3)将\(x,z\)中离\(y\)比较近的一个以\(y\)为中心移动.(当\(y-x=z-y\)时不能这样做)

 

那么有人就要问了,还有离\(y\)比较远的以\(y\)为中心移动的情况呢?这必然与(1)(2)中的一个等价.

 

因此,我们可以将状态建成一棵树,对于一个状态,(1)(2)得到的是这个状态的左右儿子,(3)得到的是这个状态的父亲.

 

那么就很容易处理了.我们类似寻找LCA的过程不断向上爬即可.但我们不是暴力的向上找父亲,而是发现连续的若干次找父亲其实就是一个做gcd的过程.利用Euclid加速即可.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<iostream>
#include<algorithm>
using namespace std;
 
typedef long long LL;
 
struct Pos{
    int x,y,z;
    Pos(){}
    Pos(int _x,int _y,int _z):x(_x),y(_y),z(_z){}
    bool operator!=(const Pos&B)const{return x!=B.x||y!=B.y||z!=B.z;}
    bool operator==(const Pos&B)const{return x==B.x&&y==B.y&&z==B.z;}
};
 
LL updep;
inline Pos up(int x,int y,int z,LL d){
    if(d==0)return Pos(x,y,z);
    if(y-x==z-y)return Pos(x,y,z);
    int d1=y-x,d2=z-y,step;
    if(d1>d2){
        if(d1%d2==0){
            step=d1/d2-1;
            if(d<=step)return updep+=d,Pos(x,x+d1-d*d2,x+d1-d*d2+d2);
            else return updep+=step,up(x,x+d2,x+2*d2,d-step);
        }
        else{
            step=d1/d2;
            if(d<=step)return updep+=d,Pos(x,x+d1-d*d2,x+d1-d*d2+d2);
            else return updep+=step,up(x,x+d1%d2,x+d1%d2+d2,d-step);
        }
    }
    else{
        if(d2%d1==0){
            step=d2/d1-1;
            if(d<=step)return updep+=d,Pos(z-(d2-d*d1)-d1,z-(d2-d*d1),z);
            else return updep+=step,up(z-2*d1,z-d1,z,d-step);
        }
        else{
            step=d2/d1;
            if(d<=step)return updep+=d,Pos(z-(d2-d*d1)-d1,z-(d2-d*d1),z);
            else return updep+=step,up(z-d2%d1-d1,z-d2%d1,z,d-step);
        }
    }
}
inline Pos up(const Pos&p,LL d){return up(p.x,p.y,p.z,d);}
 
inline void Sort(int&x,int&y,int&z){
    int d[3]={x,y,z};sort(d,d+3);x=d[0],y=d[1],z=d[2];
}
 
int main(){
    int x1,y1,z1,x2,y2,z2;
    scanf("%d%d%d%d%d%d",&x1,&y1,&z1,&x2,&y2,&z2);Sort(x1,y1,z1),Sort(x2,y2,z2);
 
    LL dep1,dep2;
    updep=0;Pos Root1=up(x1,y1,z1,1<<30);dep1=updep;
    updep=0;Pos Root2=up(x2,y2,z2,1<<30);dep2=updep;
 
    if(Root1!=Root2)puts("NO");
    else{
        puts("YES");
        if(dep1<dep2)swap(dep1,dep2),swap(x1,x2),swap(y1,y2),swap(z1,z2);
        Pos now1=up(x1,y1,z1,dep1-dep2),now2=Pos(x2,y2,z2);
 
        if(now1==now2)printf("%d",dep1-dep2);
        else{
            int L=0,R=dep2,mid;
            while(L<R){
                mid=(L+R+1)>>1;
                if(up(now1,mid)!=up(now2,mid))L=mid;else R=mid-1;
            }
            printf("%d",dep1-dep2+2*(L+1));
        }
    }
#ifndef ONLINE_JUDGE
    system("pause");
#endif
 
    return 0;
}

 

BZOJ1143:[CTSC2008]祭祀river(&&BZOJ2718) 二分图

思路:

首先是一个DAG.

然后,你要选出最多的点,使得任意两个点之间不能有到达关系.

考虑将拓扑图划分为若干条点不相交路径,那么所有的结束点之间都是不能到达的.

那么我们就要求出DAG的最小路径覆盖.

然而事实上由于是DAG,路径的点是可以相交的.于是我们通过拆点转化为一般的点不相交最小路径覆盖.

考虑给每一个点找一个入点,那么每出现一个匹配,路径的数目就会减少1.

于是利用总点数减去最大匹配即可.

#define _CRT_SECURE_NO_WARNINGS

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>

#define INF 0x3f3f3f3f

#define N 210
struct MaxflowSolver{
	static const int V=N<<1;
	static const int E=N*N<<1;
	int head[V],next[E],end[E],flow[E],ind;
	int d[V],gap[V],stk[V],top,cur[V];
	inline void reset(){ind=top=0,memset(head,-1,sizeof head);}
	inline void addedge(int a,int b,int f){int q=ind++;end[q]=b,next[q]=head[a],head[a]=q,flow[q]=f;}
	inline void make(int a,int b,int f){addedge(a,b,f),addedge(b,a,0);}
	inline void bfs(int T){
		static int q[V];int fr=0,ta=0;
		memset(d,-1,sizeof d),memset(gap,0,sizeof gap),++gap[d[T]=0],q[ta++]=T;
		while(fr^ta){
			int i=q[fr++];for(int j=head[i];j!=-1;j=next[j])if(d[end[j]]==-1)++gap[d[end[j]]=d[i]+1],q[ta++]=end[j];
		}
	}
	inline int Maxflow(int S,int T,int cnt){
		int res=0,u=S,i,Min,ins;bfs(T),memcpy(cur,head,sizeof cur);
		while(d[S]<cnt){
			if(u==T){
				for(Min=INF,i=0;i<top;++i)if(flow[stk[i]]<Min)Min=flow[stk[i]],ins=i;
				for(res+=Min,i=0;i<top;++i)flow[stk[i]]-=Min,flow[stk[i]^1]+=Min;
				u=end[stk[top=ins]^1];
			}
			if(u!=T&&!gap[d[u]-1])break;
			bool find=0;
			for(int&j=cur[u];j!=-1;j=next[j])if(flow[j]&&d[u]==d[end[j]]+1){find=1,ins=j;break;}
			if(find)cur[u]=stk[top++]=ins,u=end[ins];
			else{
				Min=cnt;for(int j=head[u];j!=-1;j=next[j])if(flow[j]&&d[end[j]]<Min)Min=d[end[j]],cur[u]=j;
				if(!--gap[d[u]])break;++gap[d[u]=Min+1];
				if(u!=S)u=end[stk[--top]^1];
			}
		}return res;
	}
}SteinsGate;

int head[N],next[N*N],end[N*N];
inline void addedge(int a,int b){static int q=1;end[q]=b,next[q]=head[a],head[a]=q++;}

int seq[N],cnt;bool vis[N];
void dfs(int x){
	vis[x]=1,seq[++cnt]=x;
	for(int j=head[x];j;j=next[j])if(!vis[end[j]])dfs(end[j]);
}
int main(){
	int n,m;scanf("%d%d",&n,&m);register int i,j;
	int a,b;while(m--)scanf("%d%d",&a,&b),addedge(a,b);

	SteinsGate.reset();
	for(i=1;i<=n;++i){
		memset(vis,0,sizeof vis);
		cnt=0,dfs(i);
		for(j=1;j<=cnt;++j)if(seq[j]!=i)SteinsGate.make(i,n+seq[j],1);
	}

	int S=0,T=2*n+1;
	for(i=1;i<=n;++i)SteinsGate.make(0,i,1);
	for(i=1;i<=n;++i)SteinsGate.make(n+i,T,1);

	printf("%d",n-SteinsGate.Maxflow(S,T,2*n+2));

#ifndef ONLINE_JUDGE
	system("pause");
#endif
	return 0;
}

BZOJ2229:[Zjoi2011]最小割 最小割树

思路:

无向图的任意两点之间的最小割可以用一颗树来表示.

构造方法:

在当前的连通集合中任取两点(若当前连通集合仅有一个点退出),并在原图中求这两点之间的最小割.

在最小割树上连接这两个点,边权为刚才求出的最小割.

将当前的连通集合根据刚才的割划分为两部分,递归处理.

 

求出最小割树之后,两个点之间的最小割即为最小割树上两个点之间的路径上的边权最小值.

 

这道题就这样水就行了.

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<climits>
#include<cstdlib>
#include<iostream>
using namespace std;
 
#define INF 0x3f3f3f3f
 
#define N 160
int n,m,G[N][N];
 
struct MaxflowSolver{
    int head[N],next[N*N<<1],end[N*N<<1],flow[N*N<<1],ind;
    int d[N],gap[N],stk[N],top,cur[N];
    inline void reset(){ind=top=0,memset(head,-1,sizeof head);}
    inline void addedge(int a,int b,int f){int q=ind++;end[q]=b,next[q]=head[a],head[a]=q,flow[q]=f;}
    inline void make(int a,int b,int f){addedge(a,b,f),addedge(b,a,0);}
    inline void make2(int a,int b,int f){addedge(a,b,f),addedge(b,a,f);}
    inline void bfs(int T){
        static int q[N];int fr=0,ta=0;
        memset(d,-1,sizeof d),memset(gap,0,sizeof gap),++gap[d[T]=0],q[ta++]=T;
        while(fr^ta){
            int i=q[fr++];for(int j=head[i];j!=-1;j=next[j])if(d[end[j]]==-1)++gap[d[end[j]]=d[i]+1],q[ta++]=end[j];
        }
    }
    inline int Maxflow(int S,int T,int cnt){
        int Min,ins,res=0,i,u=S;bfs(T),memcpy(cur,head,sizeof cur);
        while(d[S]<cnt){
            if(u==T){
                for(Min=INF,i=0;i<top;++i)if(flow[stk[i]]<Min)Min=flow[stk[i]],ins=i;
                for(res+=Min,i=0;i<top;++i)flow[stk[i]]-=Min,flow[stk[i]^1]+=Min;
                u=end[stk[top=ins]^1];
            }
            if(u!=T&&!gap[d[u]-1])break;
            bool find=0;
            for(int&j=cur[u];j!=-1;j=next[j])if(flow[j]&&d[u]==d[end[j]]+1){find=1,ins=j;break;}
            if(find)stk[top++]=cur[u]=ins,u=end[ins];
            else{
                Min=cnt;for(int j=head[u];j!=-1;j=next[j])if(flow[j]&&d[end[j]]<Min)Min=d[end[j]],cur[u]=j;
                if(!--gap[d[u]])break;++gap[d[u]=Min+1];
                if(u!=S)u=end[stk[--top]^1];
            }
        }return res;
    }
}Curse;
 
struct Graph{
    int head[N],next[N*N<<1],end[N*N<<1],ind;
    inline void reset(){ind=0,memset(head,-1,sizeof head);}
    inline void addedge(int a,int b){int q=ind++;end[q]=b,next[q]=head[a],head[a]=q;}
    inline void make(int a,int b){addedge(a,b),addedge(b,a);}
}SteinsGate;
 
int totalState[N],totalid;bool totalvis[N];
void dfs(int x){
    totalvis[x]=1,totalState[++totalid]=x;
    for(int j=SteinsGate.head[x];j!=-1;j=SteinsGate.next[j])if(!totalvis[SteinsGate.end[j]])dfs(SteinsGate.end[j]);
}
 
struct Tree{
    int head[N],next[N<<1],end[N<<1],len[N<<1],ind;
    inline void reset(){ind=0,memset(head,-1,sizeof head);}
    inline void addedge(int a,int b,int l){int q=ind++;end[q]=b,next[q]=head[a],head[a]=q,len[q]=l;}
    inline void make(int a,int b,int l){addedge(a,b,l),addedge(b,a,l);}
}T;
 
int inS[N],q[N],fr,ta;
void Solve(int State[],int n){
    if(n==1)return;
     
    register int i,j;
    Curse.reset();
    for(i=1;i<=totalid;++i)for(j=i+1;j<=totalid;++j)if(G[totalState[i]][totalState[j]])
        Curse.make2(totalState[i],totalState[j],G[totalState[i]][totalState[j]]);
     
    int res=Curse.Maxflow(State[1],State[2],totalid);
     
    T.make(State[1],State[2],res);
     
    memset(inS,0,sizeof inS);fr=ta=0;q[ta++]=State[1];
    while(fr^ta){
        i=q[fr++];inS[i]=1;for(j=Curse.head[i];j!=-1;j=Curse.next[j])if(Curse.flow[j]&&!inS[Curse.end[j]])q[ta++]=Curse.end[j];
    }
     
    int seql[N],numl=0,seqr[N],numr=0;
    for(i=1;i<=n;++i)if(inS[State[i]])seql[++numl]=State[i];else seqr[++numr]=State[i];
     
    Solve(seql,numl);
    Solve(seqr,numr);
}
 
int ans[N][N];
inline void getans(int anc,int x,int fa,int nowans){
    ans[anc][x]=nowans;
    for(int j=T.head[x];j!=-1;j=T.next[j])if(T.end[j]!=fa)getans(anc,T.end[j],x,min(T.len[j],nowans));
}
 
int seq[N*N],nums;
int main(){
    int Tec;scanf("%d",&Tec);register int i,j;int a,b,x;
    while(Tec--){
        scanf("%d%d",&n,&m);
        memset(G,0,sizeof G);while(m--)scanf("%d%d%d",&a,&b,&x),G[a][b]+=x,G[b][a]+=x;
        SteinsGate.reset();for(i=1;i<=n;++i)for(j=i+1;j<=n;++j)if(G[i][j])SteinsGate.make(i,j);
         
        memset(totalvis,0,sizeof totalvis);
        T.reset();
        for(i=1;i<=n;++i)if(!totalvis[i]){
            totalid=0;
            dfs(i);
             
            Solve(totalState,totalid);
        }
         
        nums=0;
        for(i=1;i<=n;++i){
            getans(i,i,-1,INF);
            for(j=i+1;j<=n;++j)seq[++nums]=ans[i][j];
        }
         
        int Q;scanf("%d",&Q);
        while(Q--){
            scanf("%d",&x);
            int res=0;for(i=1;i<=nums;++i)if(seq[i]<=x)++res;
            printf("%d\n",res);
        }
        puts("");
         
    }
     
    return 0;
}

BZOJ3218:a + b Problem 网络流+线段树

思路:

首先最小割的建模是显然的.

令\(S\)集合表示选择白色,\(T\)集合表示选择黑色.

则有:

\[S->i~c=w_i\]表示割掉这条边之后,\(i\)在\(T\)集合,颜色为黑色,失去了白色的价值.

同理\[i->T~c=b_i\].

接下来假设有一堆点,若这些点有一个为白色,且\(i\)为黑色,则损失价值\(p_i\).

这个怎么搞?

设一个辅助节点\(x\),让所有对\(i\)有影响的点\(j\)都连\(j->x~c=INF\).然后再连接\(x->i~c=p_i\).

这样的话,如果有某个点\(x\)选了白色(属于\(S\)集),而节点\(i\)为黑色(属于\(T\)集),那么这条\(p_i\)的边就必定需要割掉.

 

可是这样边数爆表.

注意到一堆\(INF\)的边很是浪费,于是我们用线段树优化.

首先不考虑标号的限制,只考虑\(l\leq{a}\leq{r}\)的限制.

离散化后,我们用线段树上的\(O(logn)\)个节点来连向辅助节点.

 

接下来是标号的限制...

我们用一颗可持久化线段树来维护连边即可.

(关于可持久化线段树的姿势可以看代码)

(内附良心样例)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<map>
using namespace std;
  
#define INF 0x3f3f3f3f
  
#define N 5010
int a[N],b[N],w[N],l[N],r[N],p[N];
  
int seq[N*3],id;
  
struct MaxflowSolver{
    int head[300010],next[1000010],end[1000010],flow[1000010],ind;
    int d[300010],gap[300010],cur[300010],stk[300010],top;
    inline void reset(){ind=top=0,memset(head,-1,sizeof head);}
    inline void addedge(int a,int b,int f){int q=ind++;end[q]=b,next[q]=head[a],head[a]=q,flow[q]=f;}
    inline void make(int a,int b,int f){/*printf("%d %d %d\n",a,b,f);*/addedge(a,b,f),addedge(b,a,0);}
    inline void bfs(int T){
        static int q[300010];int fr=0,ta=0;
        memset(d,-1,sizeof d),memset(gap,0,sizeof gap),++gap[d[T]=0],q[ta++]=T;
        while(fr^ta){
            int i=q[fr++];for(int j=head[i];j!=-1;j=next[j])if(d[end[j]]==-1)++gap[d[end[j]]=d[i]+1],q[ta++]=end[j];
        }
    }
    inline int Maxflow(int S,int T,int cnt){
        int res=0,i,Min,ins,u=S;bfs(T),memcpy(cur,head,sizeof cur);
        while(d[S]<cnt){
            if(u==T){
                for(Min=INF,i=0;i<top;++i)if(Min>flow[stk[i]])Min=flow[stk[i]],ins=i;
                for(res+=Min,i=0;i<top;++i)flow[stk[i]]-=Min,flow[stk[i]^1]+=Min;
                u=end[stk[top=ins]^1];
            }
            if(u!=T&&!gap[d[u]-1])break;
            bool find=0;
            for(int&j=cur[u];j!=-1;j=next[j])if(flow[j]&&d[u]==d[end[j]]+1){find=1,ins=j;break;}
            if(find){cur[u]=stk[top++]=ins,u=end[ins];}
            else{
                Min=cnt;for(int j=head[u];j!=-1;j=next[j])if(flow[j]&&d[end[j]]<Min)Min=d[end[j]],cur[u]=j;
                if(!--gap[d[u]])break;++gap[d[u]=Min+1];
                if(u!=S)u=end[stk[--top]^1];
            }
        }return res;
    }
}SteinsGate;
  
struct Node{
    int l,r;
}S[300010];int cnt;
  
int root[5010];
int Newversion(int last,int tl,int tr,int ins){
    int q=++cnt;S[q]=S[last];if(last)SteinsGate.make(last,q,INF);if(tl==tr)return q;
    int mid=(tl+tr)>>1;
    if(ins<=mid)S[q].l=Newversion(S[last].l,tl,mid,ins);else S[q].r=Newversion(S[last].r,mid+1,tr,ins);
    return q;
}
  
void Addedge(int q,int tl,int tr,int dl,int dr,int topoint){
    if(dl<=tl&&tr<=dr){if(q)SteinsGate.make(q,topoint,INF);return;}
    int mid=(tl+tr)>>1;
    if(dr<=mid)Addedge(S[q].l,tl,mid,dl,dr,topoint);
    else if(dl>mid)Addedge(S[q].r,mid+1,tr,dl,dr,topoint);
    else Addedge(S[q].l,tl,mid,dl,mid,topoint),Addedge(S[q].r,mid+1,tr,mid+1,dr,topoint);
}
  
int main(){
    int n;scanf("%d",&n);register int i,j;
    for(i=1;i<=n;++i)scanf("%d%d%d%d%d%d",&a[i],&b[i],&w[i],&l[i],&r[i],&p[i]);
  
    for(i=1;i<=n;++i)seq[++id]=a[i],seq[++id]=l[i],seq[++id]=r[i];sort(seq+1,seq+id+1);
    map<int,int>M;int num=0;for(seq[0]=-1<<30,i=1;i<=id;++i)if(seq[i]!=seq[i-1])M[seq[i]]=++num;
    for(i=1;i<=n;++i)a[i]=M[a[i]],l[i]=M[l[i]],r[i]=M[r[i]];
  
    SteinsGate.reset();
    for(i=1;i<=n;++i)root[i]=Newversion(root[i-1],1,num,a[i]),SteinsGate.make(250000+i,cnt,INF);
    for(i=1;i<=cnt;++i){
        if(S[i].l)SteinsGate.make(S[i].l,i,INF);
        if(S[i].r)SteinsGate.make(S[i].r,i,INF);
    }
  
    int S=0,T=++cnt,res=0;
    for(i=1;i<=n;++i)res+=b[i]+w[i],SteinsGate.make(S,250000+i,w[i]),SteinsGate.make(250000+i,T,b[i]);
    for(i=1;i<=n;++i)if(i)Addedge(root[i-1],1,num,l[i],r[i],++cnt),SteinsGate.make(cnt,250000+i,p[i]);
  
    printf("%d",res-SteinsGate.Maxflow(S,T,cnt+1+n));
  
#ifndef ONLINE_JUDGE
    system("pause");
#endif
  
    return 0;
}
/*10
0 1 7 3 9 2
7 4 0 9 10 5
1 0 4 2 10 2
7 9 1 5 7 2
6 3 5 3 6 2
6 6 4 1 8 1
6 1 6 0 6 5
2 2 5 0 9 3
5 1 3 0 2 5
5 6 7 1 1 2*/

 

POJ1904 King's Quest 二分图+强连通分量

题目大意:

给定一个二分图以及一组已知的完备匹配,要求对于每个\(X\)集合中的点确定这个点与哪些个\(Y\)集合中的点匹配时,依然能够存在完备匹配.

思路:

考虑完备匹配中,\(X_i->Y_i,X_j->Y_j\),那么如果现在想要\(X_i->Y_j\),那么就要保证这样做之后从\(X_j\)出发存在增广路,否则就不能形成完备匹配.

那么这条增广路的终点显然应该是\(Y_i\),也即存在必定存在路径\(X_j->Y_i\).

我们考虑将之前给出的完备匹配的反向边连回去:即若存在匹配\(X_i->Y_i\),则连接有向边\(Y_i->X_i\).

那么我们考虑有路径\(X_j->Y_i->X_i->Y_j->X_j\),就是说形成了一个环,那么这些点必定在一个强连通分量中!

考虑与某个点\(X_i\)在一个强连通分量一些\(Y\)集合的点\(Y_k\),若存在边\(X_i->Y_k\),那么就是说\(Y_k\)要么是\(X_i\)原来的完备匹配,要么可以通过交换重新找到增广路得到完备匹配.

因此我们求一次SCC就解决了这道题目.

#define _CRT_SECURE_NO_WARNINGS

#include<cstdio>
#include<cstring>
#include<climits>
#include<iostream>
#include<algorithm>

#define N 2010

namespace Fio{
	inline int getc(){
#ifdef ONLINE_JUDGE
		static const int L=1<<15;
#else
		static const int L=1<<1;
#endif
		static char buf[L],*S=buf,*T=buf;
		if(S==T){T=(S=buf)+fread(buf,1,L,stdin);if(S==T)return EOF;}
		return*S++;
	}
	inline bool digit(int c){return c>='0'&&c<='9';}
	template<typename T>inline void Get(T&x){
		int c;while(!digit(c=getc()));x=c-'0';while(digit(c=getc()))x=(x<<1)+(x<<3)+c-'0';
	}
	char buf[5000000],*o=buf;
	inline void putc(char c){*o++=c;}
	template<typename T>inline void print(T x){
		static int stk[100];int top=0;
		for(;x;x/=10)stk[++top]=x%10;for(int i=top;i>=1;--i)*o++='0'+stk[i];
	}
	inline void Final(){fwrite(buf,1,o-buf,stdout);}
}

int head[4010],next[2010*2010],end[2010*2010];
inline void addedge(int a,int b){static int q=1;end[q]=b,next[q]=head[a],head[a]=q++;}

int G[2010][2010],dfn[4010],low[4010],tclock,stk[4010],bel[4010],cnt,top;bool instk[4010];

void dfs(int x){
	dfn[x]=low[x]=++tclock;stk[++top]=x,instk[x]=1;
	for(int j=head[x];j;j=next[j]){
		if(!dfn[end[j]])dfs(end[j]),low[x]=std::min(low[x],low[end[j]]);
		else if(instk[end[j]])low[x]=std::min(low[x],dfn[end[j]]);
	}
	if(dfn[x]==low[x]){
		++cnt;
		while(1){
			int i=stk[top--];instk[i]=0;
			bel[i]=cnt;
			if(i==x)break;
		}
	}
}

int seq[2010],id;

int main(){
	int n;Fio::Get(n);register int i,j;
	int t,x;for(i=1;i<=n;++i){Fio::Get(t);while(t--)Fio::Get(x),G[i][x]=1,addedge(i,n+x);}

	for(i=1;i<=n;++i)Fio::Get(x),addedge(n+x,i);

	for(i=1;i<=2*n;++i)if(!dfn[i])dfs(i);

	for(i=1;i<=n;++i){
		for(id=0,j=1;j<=n;++j)if(bel[i]==bel[n+j]&&G[i][j])seq[++id]=j;
		Fio::print(id);
		for(j=1;j<=id;++j)Fio::putc(' '),Fio::print(seq[j]);
		Fio::putc('\n');
		//printf("%d",id);
		//for(j=1;j<=id;++j)printf(" %d",seq[j]);
		//puts("");
	}

	Fio::Final();

#ifndef ONLINE_JUDGE
	system("pause");
#endif
	return 0;
}

 

JDFZOJ2204:打地鼠 期望+凸包

思路:
首先\(O(n^3)\)的思路非常朴素,只需要考虑每条向量的贡献即可.向量的出现概率即为这两个点出现且向量右侧的点均不出现.于是我们需要暴力\(O(n)\)check右侧的点是否均不出现.

我们考虑以一个点为起点的所有向量的答案.

那么对于每一个向量右侧的点必定都是一段区间,且满足单调性.

 

我们枚举起点,然后把剩下的点按照关于起点的极角序排序,然后依次扫描即可.

当然有几个坑点:

概率有一些为0的不能直接乘除,而需要记录零的个数.

还有就是坑爹的三点共线问题.

 

我们考虑当角度相同时,按照距离从大到小排序.

然后扫描的时候进行一些特判.这里没太弄明白就先挖坑了.

(好像一开始先把点随机扰动一下应该也可以吧)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
 
typedef double f2;
 
#define N 1010
struct Point{
    f2 x,y,p;
    Point(){}
    Point(f2 _x,f2 _y):x(_x),y(_y){}
    Point operator+(const Point&B)const{return Point(x+B.x,y+B.y);}
    Point operator-(const Point&B)const{return Point(B.x-x,B.y-y);}
    Point operator*(const f2&p)const{return Point(p*x,p*y);}
}P[N];
inline f2 sqr(const f2&x){return x*x;}
inline f2 cross(const Point&a,const Point&b){return a.x*b.y-a.y*b.x;}
inline f2 dot(const Point&a,const Point&b){return a.x*b.x+a.y*b.y;}
inline f2 length(const Point&a){return sqrt(sqr(a.x)+sqr(a.y));}
 
static const f2 eps=1e-8;
inline int dcmp(const f2&x){return fabs(x)<eps?0:(x<0?-1:1);}
 
struct Seg{
    Point v;
    f2 Ang,area,p,len;
    inline bool operator<(const Seg&B)const{
        return dcmp(Ang-B.Ang)==0?len>B.len:Ang<B.Ang;
    }
}S[N];
 
int main(){
    int n;scanf("%d",&n);register int i,j,k;
    for(i=0;i<n;++i)scanf("%lf%lf%lf",&P[i].x,&P[i].y,&P[i].p),P[i].p/=1000.0;
    f2 ans=0;
    for(i=0;i<n;++i){
        int id=0;
        for(j=0;j<n;++j)if(i^j){
            S[id].v=P[i]-P[j];
            S[id].Ang=atan2(S[id].v.y,S[id].v.x);
            S[id].area=cross(P[i],P[j]);
            S[id].p=P[j].p;
            S[id].len=length(S[id].v);
            id++;
        }
        sort(S,S+id);
        long double nowp=P[i].p;
        int zero=0;
        for(j=0;j<id;++j)if(dcmp(S[j].p-1)==0)++zero;else nowp*=(1-S[j].p);
         
        k=0;
        for(j=0;j<id;++j){
            while(dcmp(cross(S[j].v,S[k].v))>0||(dcmp(cross(S[j].v,S[k].v))==0&&dcmp(dot(S[k].v,S[j].v-S[k].v))<=0)){
                if(dcmp(1-S[k].p))nowp/=1-S[k].p;else --zero;
                k=(k+1)%id;
                if(k==j)break;
            }
            if(!zero)ans+=S[j].area*nowp*S[j].p;
            if(dcmp(1-S[j].p)!=0)nowp*=(1-S[j].p);else ++zero;
        }
    }
     
    printf("%.6lf",ans*.5);
    return 0;
}

BZOJ3772:精神污染 dfs序+可持久化线段树

以后决定土豪题都粘一下题面吧.

【begin

Description

兵库县位于日本列岛的中央位置,北临日本海,南面濑户内海直通太平洋,中央部位是森林和山地,与拥有关西机场的大阪府比邻而居,是关西地区面积最大的县,是集经济和文化于一体的一大地区,是日本西部门户,海陆空交通设施发达。濑户内海沿岸气候温暖,多晴天,有日本少见的贸易良港神户港所在的神户市和曾是豪族城邑“城下町”的姬路市等大城市,还有以疗养地而闻名的六甲山地等。
兵库县官方也大力发展旅游,为了方便,他们在县内的N个旅游景点上建立了n-1条观光道,构成了一棵图论中的树。同时他们推出了M条观光线路,每条线路由两个节点x和y指定,经过的旅游景点就是树上x到y的唯一路径上的点。保证一条路径只出现一次。
你和你的朋友打算前往兵库县旅游,但旅行社还没有告知你们最终选择的观光线路是哪一条(假设是线路A)。这时候你得到了一个消息:在兵库北有一群丧心病狂的香菜蜜,他们已经选定了一条观光线路(假设是线路B),对这条路线上的所有景点都释放了【精神污染】。这个计划还有可能影响其他的线路,比如有四个景点1-2-3-4,而【精神污染】的路径是1-4,那么1-3,2-4,1-2等路径也被视为被完全污染了。
现在你想知道的是,假设随便选择两条不同的路径A和B,存在一条路径使得如果这条路径被污染,另一条路径也被污染的概率。换句话说,一条路径被另一条路径包含的概率。
 

 

Input

第一行两个整数N,M
接下来N-1行,每行两个数a,b,表示A和B之间有一条观光道。
接下来M行,每行两个数x,y,表示一条旅游线路。
 

 

Output

所求的概率,以最简分数形式输出。
 

 

Sample Input

5 3
1 2
2 3
3 4
2 5
3 5
2 5
1 4

Sample Output

1/3
样例解释
可以选择的路径对有(1,2),(1,3),(2,3),只有路径1完全覆盖路径2。

HINT

 

100%的数据满足:N,M<=100000

end】

思路:

分类讨论几种完全覆盖的情形,然后用离线+dfs序+可持久化线段树水过.时间复杂度\(O(mlogn)\).

真的想写的话自己看代码.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
 
namespace Fio{
    inline int getc(){
        static const int L=1<<15;static char buf[L],*S=buf,*T=buf;
        if(S==T){T=(S=buf)+fread(buf,1,L,stdin);if(S==T)return EOF;}
        return*S++;
    }
    inline bool digit(int x){return x>='0'&&x<='9';}
    template<typename T>inline void Get(T&x){
        int c;while(!digit(c=getc()));x=c-'0';while(digit(c=getc()))x=(x<<1)+(x<<3)+c-'0';
    }
}
 
#define N 100010
int n,m;
int head[N],next[N<<1],end[N<<1];
inline void addedge(int a,int b){static int q=1;end[q]=b,next[q]=head[a],head[a]=q++;}
inline void make(int a,int b){addedge(a,b),addedge(b,a);}
 
int pa[N][17],dep[N],in[N],out[N],tclock;
void dfs(int x,int fa){
    in[x]=++tclock;
    for(int j=head[x];j;j=next[j])if(end[j]!=fa){pa[end[j]][0]=x,dep[end[j]]=dep[x]+1,dfs(end[j],x);}
    out[x]=tclock;
}
inline int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=16;i>=0;--i)if(dep[pa[x][i]]>=dep[y])x=pa[x][i];
    if(x==y)return x;
    for(int i=16;i>=0;--i)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
    return pa[x][0];
}
 
#define M 100010
int x[M],y[M],Lca[M];
 
vector<int>v[N];
 
int root[N<<1];
struct SegmentTree{
    struct Node{
        int l,r,siz;
    }S[3000000];
    int cnt;
    inline void reset(){cnt=0;}
    inline int newnode(){int q=++cnt;S[q].l=S[q].r=S[q].siz=0;return q;}
    int Newversion(int Last,int tl,int tr,int ins){
        int q=newnode();S[q]=S[Last],++S[q].siz;if(tl==tr)return q;
        int mid=(tl+tr)>>1;
        if(ins<=mid)S[q].l=Newversion(S[Last].l,tl,mid,ins);else S[q].r=Newversion(S[Last].r,mid+1,tr,ins);
        return q;
    }
    inline int Query(int q,int tl,int tr,int dl,int dr){
        if(dl>dr)return 0;
        if(dl<=tl&&tr<=dr){return S[q].siz;}
        int mid=(tl+tr)>>1;
        if(dr<=mid)return Query(S[q].l,tl,mid,dl,dr);
        else if(dl>mid)return Query(S[q].r,mid+1,tr,dl,dr);
        else return Query(S[q].l,tl,mid,dl,mid)+Query(S[q].r,mid+1,tr,mid+1,dr);
    }
}Seg;
 
struct Path{
    int x,y;
    Path(){}
    Path(int _x,int _y):x(_x),y(_y){}
}sav[M<<1];
inline bool cmp1(const Path&A,const Path&B){return in[A.x]<in[B.x];}
inline bool cmp2(const Path&A,const Path&B){return in[A.y]<in[B.y];}
 
int seq[M<<1];
long long res;
 
void calcstep1(){
    register int i;int lans,rans,L,R,mid;
    for(int Root=1;Root<=n;++Root){
        int num=(int)v[Root].size();if(!num)continue;
        for(i=1;i<=num;++i){sav[i]=Path(x[v[Root][i-1]],y[v[Root][i-1]]);if(in[sav[i].x]>in[sav[i].y])swap(sav[i].x,sav[i].y);}
        sort(sav+1,sav+num+1,cmp1);
        Seg.reset();
        for(i=1;i<=num;++i)root[i]=Seg.Newversion(root[i-1],1,n,in[sav[i].y]);
        for(i=1;i<=num;++i)seq[i]=in[sav[i].x];
        for(i=1;i<=num;++i){
            if(in[sav[i].x]>seq[num]||out[sav[i].x]<seq[1])continue;
            for(L=1,R=num;L<R;){
                mid=(L+R)>>1;
                if(seq[mid]>=in[sav[i].x])R=mid;else L=mid+1;
            }lans=L;
            for(L=1,R=num;L<R;){
                mid=(L+R+1)>>1;
                if(seq[mid]>out[sav[i].x])R=mid-1;else L=mid;
            }rans=L;
            res+=Seg.Query(root[rans],1,n,in[sav[i].y],out[sav[i].y])-Seg.Query(root[lans-1],1,n,in[sav[i].y],out[sav[i].y])-1;
        }
    }
}
 
void calcstep2(){
    register int i;int lans,rans,L,R,mid,num=0;
    for(i=1;i<=m;++i){       
        if(x[i]==Lca[i]||y[i]==Lca[i]){sav[++num]=Path(x[i],y[i]);if(in[sav[num].x]>in[sav[num].y])swap(sav[num].x,sav[num].y);}
    }
    sort(sav+1,sav+num+1,cmp2);
    Seg.reset();
    for(i=1;i<=num;++i)root[i]=Seg.Newversion(root[i-1],1,n,in[sav[i].x]);
    for(i=1;i<=num;++i)seq[i]=in[sav[i].y];
    for(i=1;i<=num;++i){
        if(in[sav[i].y]>seq[num]||out[sav[i].y]<seq[1])continue;
        for(L=1,R=num;L<R;){
            mid=(L+R)>>1;
            if(seq[mid]>=in[sav[i].y])R=mid;else L=mid+1;
        }lans=L;
        for(L=1,R=num;L<R;){
            mid=(L+R+1)>>1;
            if(seq[mid]>out[sav[i].y])R=mid-1;else L=mid;
        }rans=L;
        res+=Seg.Query(root[rans],1,n,1,in[sav[i].x])+Seg.Query(root[rans],1,n,out[sav[i].x]+1,n);
        res-=Seg.Query(root[lans-1],1,n,1,in[sav[i].x])+Seg.Query(root[lans-1],1,n,out[sav[i].x]+1,n);
        res--;
    }
}
 
void calcstep3(){
    register int i;int lans,rans,L,R,mid,num=0;
    for(i=1;i<=m;++i)if(x[i]!=Lca[i]&&y[i]!=Lca[i])sav[++num]=Path(Lca[i],x[i]),sav[++num]=Path(Lca[i],y[i]);
    sort(sav+1,sav+num+1,cmp2);
    Seg.reset();
    for(i=1;i<=num;++i)root[i]=Seg.Newversion(root[i-1],1,n,in[sav[i].x]);
    for(i=1;i<=num;++i)seq[i]=in[sav[i].y];
    for(i=1;i<=m;++i){
        if(x[i]!=Lca[i]&&y[i]!=Lca[i])continue;
        if(in[x[i]]>in[y[i]])swap(x[i],y[i]);
        if(in[y[i]]>seq[num]||out[y[i]]<seq[1])continue;
        for(L=1,R=num;L<R;){
            mid=(L+R)>>1;
            if(seq[mid]>=in[y[i]])R=mid;else L=mid+1;
        }lans=L;
        for(L=1,R=num;L<R;){
            mid=(L+R+1)>>1;
            if(seq[mid]>out[y[i]])R=mid-1;else L=mid;
        }rans=L;
        res+=Seg.Query(root[rans],1,n,1,in[x[i]])+Seg.Query(root[rans],1,n,out[x[i]]+1,n);
        res-=Seg.Query(root[lans-1],1,n,1,in[x[i]])+Seg.Query(root[lans-1],1,n,out[x[i]]+1,n);
        if(x[i]==y[i])res-=(int)v[x[i]].size();
    }
}
 
long long gcd(long long a,long long b){return(!b)?a:gcd(b,a%b);}
 
int main(){ 
    Fio::Get(n),Fio::Get(m);register int i,j;int a,b;
    for(i=1;i<n;++i){
        Fio::Get(a),Fio::Get(b);make(a,b);
    }
    dep[1]=1,dfs(1,-1);for(j=1;j<=16;++j)for(i=1;i<=n;++i)pa[i][j]=pa[pa[i][j-1]][j-1];
     
    for(i=1;i<=m;++i){
        Fio::Get(x[i]),Fio::Get(y[i]),Lca[i]=lca(x[i],y[i]);
        if(Lca[i]!=x[i]&&Lca[i]!=y[i])v[Lca[i]].push_back(i);
    }
     
    calcstep1();
    calcstep2();
    calcstep3();
     
    if(res==0)puts("0/1");
    else{
        long long total=(long long)m*(m-1)/2;
        long long Gcd=gcd(res,total);res/=Gcd,total/=Gcd;
        printf("%lld/%lld",res,total);
    }
     
    return 0;
}