P9020 [USACO23JAN] Mana Collection P 题解

P9020 [USACO23JAN] Mana Collection P - 洛谷

首先我们可以发现贡献实际上是 \(ti_um_u\) ( \(ti_u\) 表示最后一次到达 \(u\) 的时间) ,但是最后到达性质没有最先到达好,所以将贡献转化为 \((s-ti_u)m_u\)\(ti_u\) 表示第一次到达 \(u\) 的时间),括号拆开变 \(sm_u-ti_um_u\) 。我们要求的就是 \(f(s)=min(s\sum_{u\in P}m_u-\sum_{u\in P}ti_um_u)\) ( \(P\) 表示一种移动方案 )。我们可以简单预处理出 \(\sum_{u\in P}m_u\)\(\sum_{u\in P}ti_um_u\) ( 随便 dp 一下即可) 。我们可以发现变成了一个一次函数的形式 \(k=\sum_{u\in P}m_u,b=\sum_{u\in P}ti_um_u\) ,李超线段树维护一下直线查询交点最小值即可。

注意,乘法可能爆 long long 。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int __int128
inline int read()
{
	int x=0;bool f=0;char ch=getchar();
	while(ch<'0'||ch>'9')f^=(ch=='-'),ch=getchar();
	while('0'<=ch&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int N=5e5+10,V=1e9,inf=1e18;
struct node
{
	int k,b; 
}xd[N*20];
struct stu
{
	int ls,rs,id;
}sh[N*20];
int tot,n,m,q,cnt,a[N],rt[20],sum[N],dis[20][20],f[N][20];
int calc(int id,int wz){return wz*xd[id].k+xd[id].b;}
bool kscmp(int x,int y,int wz)
{
	int xz=calc(x,wz),yz=calc(y,wz);
	if(xz<yz)return 1;
	return 0;
}
void updata(int &x,int l,int r,int id)
{
	if(!x){x=++tot;sh[x]=(stu){0,0,0};}
	int mid=(l+r)>>1;
	if(kscmp(sh[x].id,id,mid))swap(sh[x].id,id);
	if(l==r)return ;
	if(kscmp(sh[x].id,id,l))updata(sh[x].ls,l,mid,id);
	if(kscmp(sh[x].id,id,r))updata(sh[x].rs,mid+1,r,id);
	return ;
}
int query(int x,int l,int r,int wz)
{
	if(l==r)return calc(sh[x].id,l);
	int mid=(l+r)>>1;__int128 res=calc(sh[x].id,wz);
	if(wz<=mid)return max(res,query(sh[x].ls,l,mid,wz));
	else return max(res,query(sh[x].rs,mid+1,r,wz));
}
signed main()
{
	//ios::sync_with_stdio(0);
	//cin.tie(0);cout.tie(0);
	n=read();m=read();
	for(int i=1;i<=n;i++)a[(1<<(i-1))]=read();
	for(int i=1;i<(1<<n);i++)
	{
		int x=i&(-i);
		sum[i]=sum[i^x]+a[x];
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)dis[i][j]=inf;
	memset(f,0x7f,sizeof(f));
	for(int i=1;i<=m;i++)
	{
		int x,y,z;x=read();y=read();z=read();
		dis[y][x]=z;//反向边 
	}
	xd[0]=(node){0,-inf};
	for(int i=1;i<=n;i++)dis[i][i]=0,f[(1<<(i-1))][i]=0;
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	for(int s=1;s<(1<<n);s++)
		for(int i=1;i<=n;i++)
		{
			if(!(s>>(i-1)&1))continue;//状态不合法
			for(int j=1;j<=n;j++)
			{
				if((!(s>>(j-1)&1))||j==i)continue;
				f[s][i]=min(f[s][i],f[s^(1<<(i-1))][j]+sum[s^(1<<(i-1))]*dis[i][j]);//int128
			} 	
			xd[++cnt]=(node){sum[s],-f[s][i]};
			//printf("%lld %lld %lld\n",(long long)i,(long long)s,(long long)-f[s][i]);
			updata(rt[i],1,V,cnt);
		}
	q=read(); 
	while(q--)
	{
		int s,x;s=read(),x=read();
		cout<<(long long)query(rt[x],1,V,s)<<'\n'; 
	}
	return 0;
} 

总结

关键在于抽象出代价贡献形式,并转化成更易维护的形式。最后观察式子特点优化dp。

posted @ 2025-07-31 19:45  exCat  阅读(7)  评论(0)    收藏  举报