重点:如何保存背包DP,最优解的组成。
题目大意:
有n个作业,m个动作。每一个动作都只能对1个任务进行操作。给出每个动作作用的任务编号、作用时间、完成度、当一个任务的完成度达到100,则表示该任务可以被完成。
但是每个任务也都有截止时间,如果该动作无法在截止时间前完成,则失败。
输出:
如果任务无法按时完成,输出-1.
否则,第一行,输出可以完成任务的动作数目;第二行,输出动作序列。
(注:只要可以完成任务,可以输出1个任意的解决方案。也就是动作数和动作先后顺序并不做要求)
思路:
本题目可以视作01背包。
当我们把时间视为:物品的价值;完成度视为:物品的重量;背包容量:100。
接下来就是套用01背包DP就可以解决。
但是呢~~~~题目还让你输出:可以完成所有任务的动作序列。这就是——我在这题新学到的......
我们根绝可以被放入的物品的顺序,从后往前遍历。如果当前的dp[i][j]=dp[i-1][j],说明当前点的dp[i][j]的最小值,继承于i-1。如图中的x2一定是继承于x1的值。
所以,当我们遇到dp[i][j]!=dp[i-1][j],当前i,一定是最优解的构成之一。
代码:
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define endl '\n'
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define mm(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
struct point{
ll tt,res,id;
};
ll a[100010];
vector<point>vec[100010]
ll now[100010];
ll vis[110];
vector<ll>ans;
ll cc(ll x){
ll dp[vec[x].size()+10][110];
memset(dp,INF,sizeof(dp));
dp[0][0]=0;
for(ll i=0;i<vec[x].size();i++){
dp[i+1][0]=0;
ll tt=vec[x][i].tt,res=vec[x][i].res,id=vec[x][i].id;
for(ll j=1;j<=100;j++){
dp[i+1][j]=min(dp[i][j],dp[i][max(1ll*0,j-res)]+tt);
}
}
if(dp[vec[x].size()][100]==-1)return -1;
for(ll i=100,k=vec[x].size()-1;k>=0;k--){
if(dp[k+1][i]==dp[k][i])continue;
ans.push_back(vec[x][k].id);
i=max(1ll*0,i-vec[x][k].res);
}
return dp[vec[x].size()][100];
}
void solve(){
ans.clear();
ll n,m;
ll num,t,res;
cin>>n>>m;
for(ll i=1;i<=n;i++)vec[i].clear();
for(ll i=1;i<=n;i++){
cin>>a[i];
}
for(ll i=1;i<=m;i++){
cin>>num>>t>>res;
vec[num].push_back({t,res,i});
}
ll sum=0;
for(ll i=1;i<=n;i++){
now[i]=cc(i);
if(now[i]==-1){
cout<<"-1"<<endl;
return ;
}
sum+=now[i];
if(sum>a[i]){
cout<<"-1"<<endl;
return ;
}
}
cout<<ans.size()<<endl;
for(ll i=0;i<ans.size();i++)cout<<ans[i]<<" ";
cout<<endl;
}
int main(){
IOS;
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}