CCPC2018 吉林站 C.JUSTICE(思路+分数结构体加法)

4 篇文章 0 订阅
4 篇文章 0 订阅

题意

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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值