反思
A
时间花的太长了,很久不做图上问题,有些不熟练
B
考场降智,没有想清贡献如何计算最方便,然后就无法优化自己的 d p dp dp 式子
D
感觉树上路径的题很多都是点分治,而且不算太难,应该冲一冲的
题解
A
感觉是目前为止较难的
A
A
A 题(可能是我太菜了
有一个结论是:最短路图是一个
D
A
G
DAG
DAG
然后就用这一个结论令
f
i
,
j
f_{i,j}
fi,j 表示当前一个人在
i
i
i,另一个人在
j
j
j 的方案数
钦定一下转移顺序即可
时间复杂度
O
(
n
m
)
O(nm)
O(nm)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=2100,M=30100,P=1e9+9;
int n,m,S,T,dp[N][N],dis[N];
map<pair<int,int>,bool> mp;
vector<int> G[N];
priority_queue<pii,vector<pii>,greater<pii> > que;
int e[M],w[M],ne[M],h[N],idx;
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void add(int x,int y,int z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
void dij(){
memset(dis,0x3f,sizeof(dis));
dis[S]=0;que.push(make_pair(0,S));
while(!que.empty()){
int u=que.top().second;que.pop();
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(dis[u]+w[i]<dis[v]) dis[v]=dis[u]+w[i],que.push(make_pair(dis[v],v));
}
}
}
bool chkmin(int x,int y){
if(dis[x]<dis[y]||(dis[x]==dis[y]&&x<y)) return true;
return false;
}
inline void inc(int &x,int y){
x+=y;
if(x>=P) x-=P;
}
int dfs(int p1,int p2){
if(dp[p1][p2]!=-1) return dp[p1][p2];
if(p1==S&&p2==S) return 1;
if(p1==p2&&p1!=T) return 0;
dp[p1][p2]=0;
if(dis[p1]<dis[p2])
for(int i:G[p2]) inc(dp[p1][p2],dfs(p1,i));
else
for(int i:G[p1]) inc(dp[p1][p2],dfs(i,p2));
return dp[p1][p2];
}
int qmi(int a,int b){
int res=1;
for(;b;b>>=1){
if(b&1) res=1ll*res*a%P;
a=1ll*a*a%P;
}
return res;
}
int main(){
freopen("dining.in","r",stdin);
freopen("dining.out","w",stdout);
n=read(),m=read(),S=read(),T=read();
memset(h,-1,sizeof(h));
for(int i=1;i<=m;i++){
int x=read(),y=read(),z=read();
add(x,y,z);
}
dij();
int ad=0;
for(int i=1;i<=n;i++)
for(int j=h[i];~j;j=ne[j])
if(dis[i]+w[j]==dis[e[j]]&&!mp.count(make_pair(i,e[j]))){
if(i==S&&e[j]==T) ad++;
mp[make_pair(i,e[j])]=1;
G[e[j]].push_back(i);
}
memset(dp,-1,sizeof(dp));
printf("%d\n",1ll*(dfs(T,T)+ad)*qmi(2,P-2)%P);
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
B
考场降智,极为无语
一个朴素的
d
p
dp
dp 思路为令
f
i
f_i
fi 为长度为
i
i
i 的排列的权值之和
首先考虑把左右两边的贡献相加,即
f
a
b
!
+
f
b
a
!
→
f
a
+
b
+
1
f_ab!+f_ba!\to f_{a+b+1}
fab!+fba!→fa+b+1
然后考虑左右儿子的贡献,即
g
b
a
!
−
g
a
b
!
+
(
a
+
1
)
a
!
b
!
g_ba!-g_ab!+(a+1)a!b!
gba!−gab!+(a+1)a!b!,后面一部分是偏移量,需要保证
a
>
0
,
b
>
0
a>0,b>0
a>0,b>0
其中
g
i
=
∑
j
=
1
i
j
×
(
i
−
1
)
!
=
(
i
−
1
)
!
i
(
i
+
1
)
2
g_i=\sum\limits_{j=1}^i j\times (i-1)!=(i-1)!\frac{i(i+1)}{2}
gi=j=1∑ij×(i−1)!=(i−1)!2i(i+1)
这样计算时间复杂度是
O
(
n
2
)
O(n^2)
O(n2) 的
考虑优化,可以直接拆式子,然后计算,这里就不多说了,也不算很难
一个有趣的事情是拆完式子之后发现
g
b
a
!
−
g
a
b
!
g_ba!-g_ab!
gba!−gab! 的贡献可以抵消掉
时间复杂度
O
(
n
)
O(n)
O(n)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1000100;
int n,P,f[N];
int fac[N],inv[N];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
int C(int a,int b){
if(a<b) return 0;
return 1ll*fac[a]*inv[b]%P*inv[a-b]%P;
}
inline void inc(int &x,LL y){ x=(x+y)%P;}
int qmi(int a,int b){
int res=1;
for(;b;b>>=1){
if(b&1) res=1ll*res*a%P;
a=1ll*a*a%P;
}
return res;
}
int main(){
freopen("dtree.in","r",stdin);
freopen("dtree.out","w",stdout);
n=read(),P=read();
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%P;
inv[n]=qmi(fac[n],P-2);
for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%P;
int res=0;
for(int i=1;i<=n;i++){
f[i]=1ll*res*2*fac[i-1]%P;
if(i>2) inc(f[i],(1ll*(i+1)*(i-2)/2)%P*fac[i-1]);
inc(res,1ll*f[i]*inv[i]);
}
printf("%d\n",f[n]);
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}
D
看到树上路径问题,首先想到点分治
我们考虑容斥,先把以
r
t
rt
rt 为分治中心的整棵树的答案全部算出来,然后再减去两头在同一棵子树内的答案
现在的问题是:有一个序列,每个位置有
m
x
,
m
n
mx,mn
mx,mn,问有多少对位置
max
(
m
x
i
,
m
x
j
)
−
min
(
m
n
i
,
m
n
j
)
=
k
\max(mx_i,mx_j)-\min(mn_i,mn_j)=k
max(mxi,mxj)−min(mni,mnj)=k
考虑先按照
m
x
mx
mx 排序
然后分类讨论:
- m x i − m n i > k mx_i-mn_i>k mxi−mni>k,一定不可能成为答案, s k i p skip skip
- m x i − m n i < k mx_i-mn_i<k mxi−mni<k,因为前面的 m x j ≤ m x i mx_j\le mx_i mxj≤mxi,所以现在需要统计 m n j = m x i − k mn_j=mx_i-k mnj=mxi−k 的个数,开个桶即可
- m x i − m n i = k mx_i-mn_i=k mxi−mni=k,需要统计 m n j ≥ m n i mn_j\ge mn_i mnj≥mni 的个数,这也不难统计,容斥一下,求 m n j < m n i mn_j<mn_i mnj<mni 的个数即可,不难发现 m n i mn_i mni 是递增的,所以只要用几个变量维护一下
点分治的 n l o g n nlogn nlogn × \times × 排序的 l o g n logn logn,时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),常数较小,可以轻松通过
说句闲话,感觉最近点分治考得很多啊,感觉需要倒序开题了
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=200100;
int n,k,siz[N];
LL ans;
bool vis[N];
int e[N<<1],w[N<<1],ne[N<<1],h[N],idx;
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void get_size(int u,int fa){
siz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(!vis[v]&&v!=fa) get_size(v,u),siz[u]+=siz[v];
}
}
struct PATH{ int mx,mn;}path[N];
int cnt,buc[N];
void dfs(int u,int fa,int mn,int mx){
path[++cnt]={mx,mn};
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(v!=fa&&!vis[v]) dfs(v,u,min(mn,w[i]),max(mx,w[i]));
}
}
LL djx(){
sort(path+1,path+cnt+1,[](const PATH &x,const PATH &y){ return x.mx<y.mx;});
int nowv=0,nowc=0;
LL res=0;
for(int i=1;i<=cnt;i++){
if(path[i].mx-path[i].mn<k){
if(path[i].mx-k>0) res+=buc[path[i].mx-k];
}
if(path[i].mx-path[i].mn==k){//find a j satisfy path[j].mn < path[i].mn
while(nowv+1<path[i].mn) nowc+=buc[++nowv];
nowv=path[i].mn-1,res+=i-1-nowc;
}
if(path[i].mn<=nowv) nowc++;
buc[path[i].mn]++;
}
for(int i=1;i<=cnt;i++) buc[path[i].mn]=0;
return res;
}
void solve(int rt){
get_size(rt,-1);
int tot=siz[rt];
while(true){
bool flg=0;
for(int i=h[rt];~i;i=ne[i]){
int v=e[i];
if(siz[v]<siz[rt]&&siz[v]*2>=tot){ flg=1,rt=v;break;}
}
if(!flg) break;
}
vis[rt]=1;
cnt=0;
for(int i=h[rt];~i;i=ne[i]){
int v=e[i];
if(vis[v]) continue;
dfs(v,rt,w[i],w[i]);
}
for(int i=1;i<=cnt;i++) if(path[i].mx-path[i].mn==k) ans++;
// cout<<"root : "<<rt<<'\n';
// for(int i=1;i<=cnt;i++) cout<<path[i].mx<<' '<<path[i].mn<<'\n';cout<<'\n';
ans+=djx();
for(int i=h[rt];~i;i=ne[i]){
int v=e[i];
if(vis[v]) continue;
cnt=0,dfs(v,rt,w[i],w[i]);
ans-=djx();
}
for(int i=h[rt];~i;i=ne[i]) if(!vis[e[i]]) solve(e[i]);
}
void add(int x,int y,int z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
int main(){
freopen("minmax.in","r",stdin);
freopen("minmax.out","w",stdout);
n=read(),k=read();
memset(h,-1,sizeof(h));
for(int i=1;i<n;i++){
int x=read(),y=read(),z=read();
add(x,y,z),add(y,x,z);
}
solve(1);
printf("%lld\n",ans);
fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
return 0;
}