[Algospot]WITHDRAWAL
해결
처음에는 r/c가 큰 것부터 철회하면 해결 할 수 있을 줄 알았으나 이 방법으로는 답을 구할 수 없다.
질문 바꾸기
“적절히 과목을 철회했을 때 얻을 수 있는 최소 누적 등수” 라는 질문을 “적절히 과목을 철회해 누적 등수 x 이하가 될 수 있나?” 라고 변경하면 접근이 수월해 진다. 하지만 sum(r[i]/c[i])<=x 을 구하기에는 어떤 과목들을 골라야 할지 알 수 없다. 여기서 적절히 수식을 조작하면 아래와 같은 식이 나온다.
- 0<=sum(x*c[i]-r[i])
 - v[i]=x*c[i]-r[i]
 
즉, 누적 등수가 x일때 x와 전체등수를 곱한 값이 현재등수와 일치하면 x가 정확하다고 할 수 있다. 여기서 v를 정렬하고 가장 큰 k개의 원소를 더해서 쉽게 풀 수 있다.
탐욕법
v에서 k개 이상의 집합을 모두 선택해 보는 것이 아니라 가장 큰 원소 k개만 선택한다.
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n,k;
int c[1000],r[1000];
bool decision(double average)
{
    //sum(r[i]/c[i])<=x
    //sum(r[i])<=x*sum(c[i])
    //0<=sum(x*c[i]-r[i])
    //v[i]=x*c[i]-r[i]를 계산한다.
    vector<double> v;
    for(int i=0;i<n;++i)
        v.push_back(average*c[i]-r[i]);
    sort(v.begin(),v.end());
    
    //greedy
    double sum=0;
    for(int i=n-k;i<n;++i)
        sum+=v[i];
    return sum>=0;
}
double optimize()
{
    double lo=-1e-9, hi=1;
    for(int it=0;it<100;++it)
    {
        double mid=(lo+hi)/2.0;
        if(decision(mid))
            hi=mid;
        else
            lo=mid;
    }
    return hi;
}
int main()
{
    int testCase;
    cin>>testCase;
    while(testCase--)
    {
        cin>>n>>k;
        for(int i=0;i<n;++i)
            cin>>r[i]>>c[i];
        
        printf("%.10lf \n",optimize());
    }
    return 0;
}
      
댓글남기기