CCPC2018 吉林站 C.JUSTICE(思路+分數結構體加法)

題意

給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???唉。。還是太菜了呀。。

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