題意
給n件東西,第i件的重量爲1 / 2^ki,問能否分成兩堆,使每一堆的重量都>=0.5,如果能,輸出YES並在第二行輸出分配方案。如果不能,輸出NO
數據規模
1 <= T <= 2000
1 <= n <= 1e5
n總和 <= 7e5
1 <= ki <= 1e9
樣例輸入
3
3
2 2 2
3 2 2 1
2
1 1
樣例輸出
Case 1: NO
Case 2: YES
001
Case 3: YES
10
思路
賽前做了蔡隊出的牛客小白賽的題:https://www.nowcoder.com/acm/contest/190/G
隊友說,近似重量地分成兩堆的情況是最貪心的,這樣說的話,感覺JUSTICE這道題跟蔡隊出的這個題還是蠻類似的。於是開始回憶自己的做法。。突然想起蔡隊出的小白賽數據規模很小,水過去的。代碼如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100010;
const double eps = 1e-8;
const int INF = 0x3f3f3f3f;
set<int>s;
vector<int>v;
void redirect(){
#ifdef LOCAL
freopen("test.txt","r",stdin);
#endif
}
int main(){
redirect();
int n,x,T,sum = 0;
scanf("%d",&n);
for(int i = 0;i < n;i++){
scanf("%d",&x);
sum += x;
v.clear();
for(auto it = s.begin();it != s.end();it++)
v.push_back(*it + x);
for(auto it = v.begin();it != v.end();it++)
s.insert(*it);
s.insert(x);
}
int mid = sum/2,p = 0;
while((x = mid-p++) >= 0){
if(s.count(x)){
printf("%d %d\n",x,sum-x);
return 0;
}
}
return 0;
}
賽時還對這個代碼進行了重現,幾乎寫完的時候才意識到,這個寫法就算時間空間過得去,也不能記錄選擇方案。遂捨棄(打鐵可真難受
下面纔是正確 的做法(可不一定,畢竟我沒法去judge一個賽後寫出來的代碼的正確性)
結論:當且僅當總質量小於1時無解,當總質量不小於1時,總能選出一個集合,其總質量爲1/2.
證明:
令x + 1/2^k = y (x < 1/2 <= y)
1/2^k | x 並且1/2^k | 1/2
從而1/2^k | (1/2 - x) <= (y-x) = 1/2^k
y = 1/2
到了這裏還面臨一個卡精度的問題,因爲1<<1e5是算不出來的,同樣1/2^1e5也是算不出來的,如果有以下這種BT樣例:
1
100000
1 2 18 18 18……18(1e5-2個18)
總值 = 1/2 + 1/4 + (1e5-2)/2^18 > 1
而捨棄1/2^1e5時計算出來的結果時 1/2 + 1/4 < 1
所以我就杜撰出來一個1/2^k的分數加法:
舉個栗子:
1/2^2 + 1/2^3 = 2^0/2^2 + 2^0/2^3 = (2^(3-2) + 2^0)/2^3
可以發現分子是一系列2^x的和,我們可以用set之類結構存起來這些x
假如某次通分會產生兩個相同的2^x,採取的策略是消去x,添加一個x+1
這個過程重複執行,直到不存在相同的x
分母是一個2^y,爲了不計算出來具體值,只記錄分母的y值。
可以發現,假如y-max(xi) <= 1,則代表這個分數>= 0.5,如果x>=y,則代表這個數>=1
代碼實現:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
static const int maxn = 100010;
static const double eps = 1e-8;
static const int INF = 0x3f3f3f3f;
static const double pi = acos(-1);
void redirect(){
#ifdef LOCAL
freopen("test.txt","r",stdin);
#endif
}
bool flag;//判斷sum是否已經過了1/2
int ans[100010];//記錄分配方案
struct node{//分數,son是分子,mom是分母
set<int>son;
int mom;
void clean(){
son.clear();
mom = 0;
}
}p;
struct Num{//爲了分數從大到小相加,需要有一個排序,爲了id和數對得上,搞一個結構體。
int id,val;
bool operator <(const Num& b)const{
return this->val < b.val;
}
}num[100010];
inline int get_max(){//獲取分子中2的最高次項的次數
auto it = p.son.end();
it--;
return *it;
}
inline void add(const int& s){//現有分數p+一個新的分數
if(p.mom < num[s].val){//現有分數p的分子比新加的分數的分子小,現有分數p的分子多項式要乘以一個2^tmp,每一項次數+tmp,再加一個0
int tmp = num[s].val - p.mom;
p.mom = num[s].val;
vector<int>v;//臨時存儲用
for(auto it = p.son.begin();it != p.son.end();it++)
v.push_back(*it + tmp);
p.son.clear();
p.son.insert(0);
for(auto it = v.begin();it != v.end();it++)
p.son.insert(*it);
}
else{//現有分數的分子大,很舒服,新加的分數分子2^0乘一個2^tmp加到分子多項式中
int tmp = p.mom - num[s].val;
while(p.son.count(tmp))p.son.erase(tmp++);
p.son.insert(tmp);
}
if(flag == false){//sum過半則不再標記ans
ans[num[s].id] = 1;
if(p.mom - get_max() <= 1)flag = true;
}
}
int main(){
redirect();
int n,T,x;
scanf("%d",&T);
for(int t = 1;t <= T;t++){
memset(ans,0,sizeof(ans));
flag = false;
p.clean();
printf("Case %d: ",t);
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&x);
num[i].id = i;
num[i].val = x;
}
sort(num+1,num+1+n);
p.mom = num[1].val;
p.son.insert(0);
ans[num[1].id] = 1;
if(p.mom - get_max() <= 1)goto label;//用goto隊友別罵我
for(int i = 2;i <= n;i++)add(i);
if(get_max() < p.mom){
puts("NO");
continue;
}
label:
puts("YES");
for(int i = 1;i <= n;i++)printf("%d",ans[i]);
puts("");
}
return 0;
}
總之,這回現場賽CDEI題真讓人自閉了,天災人禍+flag飛起,說好的不打鐵呢?結果止步於39’兩道簽到題A,B???唉。。還是太菜了呀。。