1. 程式人生 > 實用技巧 >P3537 [POI2012]SZA-Cloakroom (揹包)

P3537 [POI2012]SZA-Cloakroom (揹包)

  • 有n件物品,每件物品有三個屬性a[i], b[i], c[i] (a[i]<b[i])。
  • 再給出q個詢問,每個詢問由非負整數m, k, s組成,問是否能夠選出某些物品使得:
    1.對於每個選的物品i,滿足a[i]<=m且b[i]>m+s。
    2.所有選出物品的c[i]的和正好是k。

輸入格式

  • 第一行一個正整數 n(n<=1,000),接下來 n 行每行三個正整數,分別表示 c[i],a[i],b[i] (c[i]<=1,000,1<=a[i]<b[i]<=109)。
  • 下面一行一個正整數 q(q<=1,000,000),接下來 q 行每行三個非負整數 m,k,s(1<=m<=109,1<=k<=100,000,0<=s<=109)。

輸出格式

  • 輸出 q 行,每行為 "TAK "(yes)或"NIE"(no),第 i 行對應第 i 此詢問的答案。

樣例

樣例輸入

5
6 2 7
5 4 9
1 2 4
2 5 8
1 3 9
5
2 7 1
2 7 2
3 2 0
5 7 2
4 1 5

樣例輸出

TAK
NIE
TAK
TAK
NIE

演算法分析

  • 思維含量蠻高的一個 暴力很好想的
  • 先來講講暴力的思想: 按照題目描述很容易我們會想到揹包問題 直接按照揹包問題的格式跑就好了 如果當前物品的a[i] > m || b[i] < m + s 那麼就讓當前狀態揹包繼承上一狀態,不然就可以試著轉移 選擇最優,每次按照k的容量跑揹包,如果最後最大價值是k 那麼就表示當前物品可以,裸的板子
  • 但是再看複雜度 一個q的詢問就如此之大了 顯然不能直接跑揹包 所以應該怎麼做?
  • 我們可以把線上詢問轉化為離線做法 然後就很容易做了
  • 對於第一個條件a[i] <= m 我們可以把a[i] 和 m 都升序排列一下
  • 關於第二個條件b[i] > m + s 我們可以用一個數組來維護組成數字之和為k的物品中最小的b屬性(如果它都滿足顯然其它的都滿足)
  • 定義陣列f[i] 表示 c屬性之和為i的幾個物品(而且滿足a屬性)的b屬性的最小值

程式碼展示



#include<bits/stdc++.h>
using namespace std;
const int maxv = 1e6+10;
int n,m,sum;
int f[maxv * 10];
int ans[maxv * 10];

struct node{
	int a,b,c;
}a[maxv];

struct Node{
	int m,k,s,id;
}b[maxv * 10];

bool cmp(node a,node b){return a.a < b.a;}
bool Cmp(Node a,Node b){return a.m < b.m;}

int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;++i)scanf("%d%d%d",&a[i].c,&a[i].a,&a[i].b);
	scanf("%d",&m);
	for(int i = 1;i <= m;++i){
		scanf("%d%d%d",&b[i].m,&b[i].k,&b[i].s);
		b[i].id = i;//記錄詢問順序防止排序排亂
	}
	sort(a + 1,a + n + 1,cmp);
	sort(b + 1,b + m + 1,Cmp);
	f[0] = 0x3f3f3f3f;
	int j = 1;
	for(int i = 1;i <= m;++i){
		while(j <= n && a[j].a <= b[i].m){
			for(int k = 100000;k >= a[j].c;--k)
				f[k] = max(f[k],min(f[k - a[j].c],a[j].b));
			++j;
		}
		if(f[b[i].k] > b[i].m + b[i].s)ans[b[i].id] = 1;//如果最小的都滿足
	}
	for(int i = 1;i <= m;++i){
		if(ans[i])printf("TAK\n");
		else printf("NIE\n");
	}
}