cf102012J. Rikka with An Unnamed Temple

该博客介绍了如何解决一个与有向无环图(DAG)相关的问题,即求从起点到终点,路径余数为特定值的最大路径和及其方案数。博主提出使用动态规划(DP)策略,通过前缀DP和后缀DP来处理不经过某个节点的情况,并利用线段树进行高效更新。此外,还需要特别处理拓扑序小于起点和大于终点的节点。代码中展示了详细的实现过程,包括状态转移方程和线段树的构建与更新。整个算法的时间复杂度为O(nlogn)。
摘要由CSDN通过智能技术生成
题目描述
题解

考虑 dp \text{dp} dp f i , j f_{i,j} fi,j 表示从 1 1 1 到第 i i i 个点,余数为 j j j 的最大值和方案数。

因为是 DAG \text{DAG} DAG ,所以 dp \text{dp} dp 是按照拓扑序转移的。

所以可以考虑做一个前缀的 dp \text{dp} dp 和后缀的 dp \text{dp} dp ,如果不经过点 i i i ,那就意味着有一次转移跨过了 i i i

因此枚举每条边,它产生的答案就可以贡献给这两个点之间的点。

用线段树维护即可。效率: O ( n l o g n ) O(nlogn) O(nlogn)

需要特判一下拓扑序小于 1 1 1 和大于 n n n 的点。

代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e5+5,M=2e5+5,P=1e9+7;
int T,n,m,w[N],c[N],in[N],V[2][M],nx[2][M],t,hd[2][N],q[N],a,b,p[N];
struct O{
	LL v;int w;
	void hb(LL _v,int _w){
		if (_v>v) v=_v,w=0;
		if (_v==v) (w+=_w)%=P;
	}
}f[2][N][100],g[N<<2],h[N];
void add(int o,int u,int v){
	nx[o][t]=hd[o][u];V[o][hd[o][u]=t]=v;
}
#define Ls k<<1
#define Rs k<<1|1
#define mid ((l+r)>>1)
void build(int k,int l,int r){
	g[k]=(O){-1,0};
	if (l==r) return;
	build(Ls,l,mid);build(Rs,mid+1,r);
}
void upd(int k,int l,int r,int L,int R,O F){
	if (L<=l && r<=R) return g[k].hb(F.v,F.w);
	if (mid>=L) upd(Ls,l,mid,L,R,F);
	if (mid<R) upd(Rs,mid+1,r,L,R,F);
}
void go(int k,int l,int r,O F){
	if (~g[k].v) F.hb(g[k].v,g[k].w);
	if (l==r){h[q[l]]=F;return;}
	go(Ls,l,mid,F);go(Rs,mid+1,r,F);
}
void work(){
	scanf("%d%d",&n,&m);t=0;
	for (int i=1;i<=n;i++)
		scanf("%d%d",&w[i],&c[i]),
		hd[0][i]=hd[1][i]=in[i]=0;
	for (int i=1,u,v;i<=m;i++)
		scanf("%d%d",&u,&v),t++,
		add(0,u,v),add(1,v,u),in[v]++;
	scanf("%d%d",&a,&b);
	for (int j=0;j<a;j++)
		for (int i=1;i<=n;i++)
			f[0][i][j]=f[1][i][j]=(O){-1,0};
	f[0][1][w[1]%a]=(O){c[1],1};
	f[1][n][w[n]%a]=(O){c[n],1};t=0;
	for (int i=1;i<=n;i++)
		if (!in[i]) q[++t]=i;
	for (int u,i=1;i<=t;i++){
		p[u=q[i]]=i;
		for (int i=hd[0][u];i;i=nx[0][i])
			if (!(--in[V[0][i]])) q[++t]=V[0][i];
	}
	for (int i=1,u;i<=n;i++)
		for (int j=hd[1][u=q[i]],v;j;j=nx[1][j])
			for (int k=0,l;k<a;k++) if (~f[0][v=V[1][j]][k].v)
				l=(k+w[u])%a,f[0][u][l].hb(f[0][v][k].v+c[u],f[0][v][k].w);
	for (int i=n,u;i;i--)
		for (int j=hd[0][u=q[i]],v;j;j=nx[0][j])
			for (int k=0,l;k<a;k++) if (~f[1][v=V[0][j]][k].v)
				l=(k+w[u])%a,f[1][u][l].hb(f[1][v][k].v+c[u],f[1][v][k].w);
	build(1,1,n);
	for (int u=1;u<=n;u++)
		for (int i=hd[0][u],v;i;i=nx[0][i]){
			v=V[0][i];
			if (p[u]+1<p[v]){
				O F=(O){-1,0};
				for (int k=0,l;k<a;k++){
					l=(b-k+a)%a;
					if ((~f[0][u][k].v) && (~f[1][v][l].v))
						F.hb(f[0][u][k].v+f[1][v][l].v,1ll*f[0][u][k].w*f[1][v][l].w%P);
				}
				if (~F.v) upd(1,1,n,p[u]+1,p[v]-1,F);
			}
		}
	if (p[1]>1) upd(1,1,n,1,p[1]-1,f[0][n][b]);
	if (p[n]<n) upd(1,1,n,p[n]+1,n,f[0][n][b]);
	go(1,1,n,(O){-1,0});
	for (int i=1;i<=n;i++)
		if (~h[i].v) printf("%lld %d\n",h[i].v,h[i].w);
		else puts("-1");
}
int main(){for (scanf("%d",&T);T--;work());return 0;}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值