题解 答题

传送门

其实就是求用这些题目能拼出来的所有得分中第 \(k\) 大的是多少
于是 \(2^n\) 的状压很好想,但 \(n\) 有40

  • \(n\leqslant 40\) 又是拼数还要求拼出的所有数中的第 \(k\) 大肯定是meet in middle啊

于是考虑用前一半数能拼出来的所有数和用后一半数能拼出来的所有数,二分 \(k\),双指针check即可

Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
#define fir first
#define sec second
#define make make_pair
//#define int long long

int n, m;
const double eps=1e-8;
double p;
int a[N];

namespace task1{
	int top;
	pair<int, double> sta[(1<<20)+100];
	void solve() {
		int lim=1<<n;
		double each=1.0/lim;
		for (int s=0,sum; s<lim; ++s) {
			sum=0;
			for (int i=0; i<n; ++i) if (s&(1<<i)) sum+=a[i+1];
			sta[++top]=make(sum, each);
		}
		sort(sta+1, sta+top+1);
		for (int i=1; i<=top; ++i) {
			if ((1.0*i)/lim >= p) {
				printf("%d\n", sta[i].fir);
				exit(0);
			}
		}
	}
}

namespace task2{
	ll buc[40010];
	void solve() {
		int lim=0;
		buc[0]=1;
		for (int i=1; i<=n; ++i) {
			for (int j=lim; ~j; --j) if (buc[j]) buc[j+a[i]]+=buc[j];
			lim+=a[i];
		}
		p*=(1ll<<n);
		ll sum=0;
		for (int i=0; i<=lim; ++i) {
			sum+=buc[i];
			if (sum>=p) {
				printf("%d\n", i);
				exit(0);
			}
		}
	}
}

namespace task{
	int b[1<<21], c[1<<21], k1, k2, lim1, lim2;
	ll check(ll k) {
		// cout<<"check: "<<k<<endl;
		int p1=1, p2=lim2; ll ans=0;
		for (; p1<=lim1&&p2; ++p1) {
			// cout<<"p: "<<p1<<' '<<p2<<endl;
			while (p2>0 && b[p1]+c[p2]>k) --p2;
			// cout<<"p2: "<<p1<<' '<<p2<<endl;
			ans+=p2;
		}
		// cout<<"return: "<<ans<<endl;
		return ans;
	}
	void solve() {
		k1=n/2, k2=n-k1, lim1, lim2;
		lim1=1<<k1; for (int s=0; s<lim1; ++s) for (int i=0; i<k1; ++i) if (s&(1<<i)) b[s+1]+=a[i+1];
		lim2=1<<k2; for (int s=0; s<lim2; ++s) for (int i=0; i<k2; ++i) if (s&(1<<i)) c[s+1]+=a[i+k1+1];
		sort(b+1, b+lim1+1); sort(c+1, c+lim2+1);
		// cout<<"b: "; for (int i=1; i<=lim1; ++i) cout<<b[i]<<' '; cout<<endl;
		// cout<<"c: "; for (int i=1; i<=lim2; ++i) cout<<c[i]<<' '; cout<<endl;
		ll l=0, r=4e7, mid, k=ceil(p*(1ll<<n));
		// cout<<"k: "<<k<<endl;
		while (l<=r) {
			mid=(l+r)>>1;
			if (check(mid)>=k) r=mid-1;
			else l=mid+1;
		}
		printf("%lld\n", l);
		exit(0);
	}
}

signed main()
{
	freopen("answer.in", "r", stdin);
	freopen("answer.out", "w", stdout);

	cin>>n>>p;
	for (int i=1; i<=n; ++i) cin>>a[i], m=max(m, a[i]);
	if (p<eps) {puts("0"); return 0;}
	// if (n<=20) task1::solve();
	// else if (m<=1000) task2::solve();
	task::solve();

	return 0;
}
posted @ 2021-10-16 06:20  Administrator-09  阅读(1)  评论(0编辑  收藏  举报