20230929 比赛总结

反思

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 
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=1ij×(i1)!=(i1)!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;
}

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 排序
然后分类讨论:

  1. m x i − m n i > k mx_i-mn_i>k mximni>k,一定不可能成为答案, s k i p skip skip
  2. m x i − m n i < k mx_i-mn_imximni<k,因为前面的 m x j ≤ m x i mx_j\le mx_i mxjmxi,所以现在需要统计 m n j = m x i − k mn_j=mx_i-k mnj=mxik 的个数,开个桶即可
  3. m x i − m n i = k mx_i-mn_i=k mximni=k,需要统计 m n j ≥ m n i mn_j\ge mn_i mnjmni 的个数,这也不难统计,容斥一下,求 m n j < m n i mn_jmnj<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 
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 : "<
    // for(int i=1;i<=cnt;i++) cout<
	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;
}

你可能感兴趣的:(其他,算法)