P2946 Cow Frisbee Team S

P2946 Cow Frisbee Team S


0.總結

這是一道好題 (我太菜了),這是一道好題(我太菜了),這是一道好題(我太菜了)!重要的話說三遍。

Get to the key point firstly, the article comes from LawsonAbs!

坑點如下:

  • 數據範圍過大,導致無法開出2000*100000的數組,即樸素的0/1揹包無法解決問題
  • 在把能力對F取模處理之後,利用一維的滾動數組會造成問題的錯誤解
  • 答案要對 10^8 取模

下面正式進入主題


1.題意

給出兩個整數n,f。接下來輸入n個數,要求從這 n 個數中選出若干個數,它們組成的和能夠整除f,求這種選擇的個數。又因爲解的數過大,要求對10^8取模。

2.主要思路

一眼望去,滿屏都是 “0/1揹包”的身影。思路如下:

  • 01.令 dp[i] 表示到達能力i的選擇種類數。初始時dp[0] = 1
  • 02.雙重for循環後,依次放入第1-n個物品 【其下標對應分別是0 — n-1】,倒序更新j的值,然後依次得到到達能力j的選擇種類。
  • 03.最後再用一個for循環判斷每個能力值是否可以整除f。如果是累計答案,同時將答案取模。
  • 04.輸出結果 res.

興奮的10min編碼,但是卻只能得到 80分,【另外兩個分別是 RE,TLE】。於是找問題。

結合題目的數據範圍,可以看到

N(1≤N≤2000);Ri(1≤Ri≤100000);F(1≤F≤1000)

那麼能力值的範圍就是 2000 * 100000。毫無疑問,開這麼大的數組肯定會MLE。那接下就要想該怎麼優化了。

題目要求能力值能整除f時,才作爲結果的一部分。根據加法模運算的規則【(a+b)%F = a%F + b%F】那麼就可以考慮

是否不用把所有的能力加起來之後對F取模,而是在相加的同時就對F取模。

這樣最後只需要輸出 dp[0] - 1【F!=1時】 或者dp[0]【F=1時】 即可。舉例來說:

2 5
1 
10

可以到達的能力是
dp[0]=1; dp[1]=1; dp[10]=1; dp[11] = 1;
其中符合輸出條件的只有10一個。而如果我們在對求出的能力值對F取餘再寫入數組時,就得到如下的結果
dp[0]=1; dp[1]=2; dp[10]=1;
dp[1]由之前的1變成了2。但是結果並不會發生變化【不會發生變化的原因就是 加法模運算這個規則造成的】。所以現在可以確定的是:

  • 利用模運算,將能力值範圍從 0- N*Ri 縮小到 0-F

但是像上面說的這樣硬性取模真的可以解決問題嗎?再看一組測試用例:

6 5
1
3
10
2
8
2

輸入時對數據同時進行取餘操作,得到的樣例就變成:

6 5
1
3
0
2
3
2

dp的過程是:用上一輪的dp[j]dp[j-arr[i]] 求和作爲本輪的dp[j],其中必有 j>arr[i] ,但是因爲我們對和進行了取模操作,就無法保證j>arr[i] 恆成立。比如10對5取模成了0,2對5取模成了2。0<2,但是10>2。所以直接用式子 dp[j] += dp[j-arr[i]] 求,就相當於是 dp[0] += dp[-2] 顯然是不合理的。所以需將操作變成 dp[j] += dp[(j-arr[i]+f)%f] 但這樣依然無法解決問題。因爲((j-arr[i]+f)%f)這個操作會改變j與arr[i]之間的大小關係。原本j大於arr[i], 可能在操作之後, arr[i]大於j,就導致之前做的倒序枚舉失效,從而無法得到正確的結果,所以在此基礎上,應該使用2維dp,將上一次的結果值放在一個數組裏,而避免出現錯誤。

3.AC代碼

#include<iostream>
using namespace std;
const int maxN = 2005;
const int maxV = 1005; 
int n,f;
int arr[maxN],dp[2][maxV];

int main(){	
	cin >> n >>f;	
	for(int i = 0 ;i< n;i++){
		cin >>arr[i];
		arr[i] %= f; 
	}
		
	fill(dp[0],dp[0]+2*maxV,0);
	
	//1.初始化,放第1個物品【即arr[0]達到的情況】 
	dp[0][0] = 1;
	dp[0][arr[0]] = 1;
	
	//2.開始依次將第2-(n-1)個物品放入 
	for(int i = 1;i< n;i++){		
		for(int j = f-1;j >= 0;j--){											
			dp[i&1][j] = dp[(i-1)&1][(j-arr[i]+f)%f] + dp[(i-1)&1][j];
			dp[i&1][j] %= 100000000;						
		}
	}
	int ei = (n-1)&1;
	int res = dp[ei][0];
	if(f == 1)
		cout << res<<"\n";
	else
		cout << res - 1<<"\n";
	return 0;
}

4.測試用例

2 1
1
1

2
2 2
1
1

1
1 1
1

1
6 5 
1
3 
10
2 
8 
2

13
4 5 
1 
2 
8 
2 

3
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章