正题
题目链接:https://ac.nowcoder.com/acm/contest/11169/E
题目大意
给出nnn个三元组(ai,bi,ci)(a_i,b_i,c_i)(ai,bi,ci)。
要求选出一个集合SSS,要求
(∑i∈Sai)≤P,(∑i∈Sbi)≥P\left(\sum_{i\in S}a_i\right)\leq P,\left(\sum_{i\in S}b_i\right)\geq P(i∈S∑ai)≤P,(i∈S∑bi)≥P
且最小化∑i∈Sci\sum_{i\in S}c_i∑i∈Sci
1≤T≤5,1≤n≤1000,1≤P≤10000,1≤ai≤bi≤2×106,1≤ci≤2×1061\leq T\leq 5,1\leq n\leq 1000,1\leq P\leq 10000,1\leq a_i\leq b_i\leq 2\times 10^6,1\leq c_i\leq 2\times 10^61≤T≤5,1≤n≤1000,1≤P≤10000,1≤ai≤bi≤2×106,1≤ci≤2×106
解题思路
暴力的思路是设fi,l,rf_{i,l,r}fi,l,r表示到第iii个,aia_iai的和为lll,bib_ibi的和为rrr时的最小值。
但是这个O(nP2)O(nP^2)O(nP2)的显然不行,发现有一个ai≤bia_i\leq b_iai≤bi的性质考虑怎么使用。
其实还要一个相关的性质就是两个的限制的PPP是相等的,虽然看起来比较废话但确实是有用的。
一个十分巧妙的dpdpdp是设fi,jf_{i,j}fi,j表示aia_iai的和≤j\leq j≤j且bib_ibi的和≥j\geq j≥j。虽然这样的限制不完全,但是这样确实可以统计到最小答案且不会统计到更小答案。
转移就是
fi,j=min{fi−1,j,fi−1,p+ci}(p∈[j−bi,j−ai])f_{i,j}=min\{f_{i-1,j},f_{i-1,p}+c_i\}(p\in[j-b_i,j-a_i])fi,j=min{fi−1,j,fi−1,p+ci}(p∈[j−bi,j−ai])
这样每一层用单调队列维护就可以了
时间复杂度O(TnP)O(TnP)O(TnP)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1100;
ll T,n,P,a[N],b[N],c[N],f[N][N*10],q[N*10];
signed main()
{scanf("%lld",&T);while(T--){scanf("%lld%lld",&n,&P);for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);for(ll i=1;i<=n;i++)scanf("%lld",&b[i]);for(ll i=1;i<=n;i++)scanf("%lld",&c[i]);memset(f,0x3f,sizeof(f));f[0][0]=0;for(ll i=1;i<=n;i++){ll tail=0,head=1,z=-1;for(ll j=0;j<=P;j++){f[i][j]=f[i-1][j];ll l=j-b[i],r=j-a[i];while(z<r){z++;if(f[i-1][z]>=1e18)continue;while(head<=tail&&f[i-1][q[tail]]>f[i-1][z])tail--;q[++tail]=z;}while(head<=tail&&q[head]<l)head++;if(head<=tail)f[i][j]=min(f[i][j],f[i-1][q[head]]+c[i]);}}if(f[n][P]>=1e18)printf("IMPOSSIBLE!!!\n");else printf("%lld\n",f[n][P]); }return 0;
}