时间花的太长了,很久不做图上问题,有些不熟练
考场降智,没有想清贡献如何计算最方便,然后就无法优化自己的 d p dp dp 式子
感觉树上路径的题很多都是点分治,而且不算太难,应该冲一冲的
感觉是目前为止较难的 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
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;
}
考场降智,极为无语
一个朴素的 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
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;
}
看到树上路径问题,首先想到点分治
我们考虑容斥,先把以 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 排序
然后分类讨论:
点分治的 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
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 : "<