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。

浙公网安备 33010602011771号