牛客练习赛86题解 A~D

题目传送门

A题

思路:
由于n很小,所以我赛中用预处理状态转移写的,因为判断当前状态时,你肯定会选择能转移到的最有利状态。
但是实际上可以发现偶数先手必胜,奇数后手必胜,因为奇数取完一定会变成偶数,偶数拿1就会变成奇数。

bool f[N]; 

void init()
{
	f[2] = true;
	f[4] = true;
	for(int i = 5 ; i <= 1e3 ; i ++)
	{
		int t = i;
		for(int j = 2 ; j <= sqrt(t) ; j ++)
			if(t % j == 0)
			{
				f[t] = !f[t - j];
				if(f[t]) break;
				f[t] = !f[t - (t / j)];
				if(f[t]) break;
			}
	}
}

void solve()
{
	init();
	int n;
	cin >> n;
	if(f[n]) cout << "Alice";
	else cout << "Bob";
} 

int main()
{
	IOS;
	solve();		 
	return 0;
}

B题

思路:
因为K只取0、1、2,N最大只有100,因此对于每一种K,专门制定一种构造方案即可
K取2的只需要让 A + B = C中 A和B的所有数字相同,就可以了。
比如 1111112 这个串, 可以分解为111 + 1 = 112和1 + 111 = 112
K取其他值的时候只需要规避K只唯一,不会更大即可

void solve()
{
    int k, n;
    cin >> k >> n;
    if(k == 1)
    {
        int d = 1;
        for(int i = 1 ; i <= n ; i ++)
        {
            if(i / 10 == d) d ++;
            cout << d << i << d + i << "\n";
        }
    }
    else if(k == 0)
    {
        for(int i = 2 ; i <= n + 1 ; i ++)
            cout << 10 << i << "\n";
    }
    else
    {
        for(int i = 2 ; n ; i ++)
        {
            for(int j = 1 ; n && j < 10 ; j ++)
            {
                cout << j;
                ll d = j, cnt = i;
                while(cnt > 1)
                    d = d * 10 + j, cnt --;
                cout << d << j + d << "\n";
                n --;
            }
        }
    }
}

int main()
{
    IOS;
    solve();    
    return 0;
}

C题

思路:
这个题有一点阴间,赛中大体思路是想到了,细节没写清楚
只需要预处理出不超过每一种钞票面额金钱的最大可取钞票数以及所取对应金额
然后当给你一个金额时,只需要二分找到当前金额对应的最大可取钞票数,然后再处理余钱即可

ll w[N];
ll cnt[N]; //cnt[i]表示不超过w[i]最大钞票数
ll use[N]; //use[i]表示对应cnt[i]的钞票面额之和
int n, m;
 
void init()
{
    cnt[1] = 0;
    use[1] = 0;
     
    for(int i = 2 ; i <= n ; i ++)
    {
        ll d = (w[i] - 1) % w[i - 1];
        int l = 1, r = i - 1;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(d >= use[mid]) l = mid;
            else r = mid - 1;
        }
        ll a = (w[i] - 1) / w[i - 1] + cnt[r];
        ll b = ((w[i] - 1) / w[i - 1] - 1) + cnt[i - 1];
        if(a > b)    cnt[i] = a, use[i] = (w[i] - 1) / w[i - 1] * w[i - 1] + use[r];
        else cnt[i] = b, use[i] = ((w[i] - 1) / w[i - 1] - 1) * w[i - 1] + use[i - 1];
    }
}
 
void solve()
{
    cin >> n;
    for(int i = 1 ; i <= n ; i ++) cin >> w[i];
    init();
    cin >> m;
    while(m --)
    {
        ll x;
        cin >> x;
        int l = 1, r = n;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(x >= use[mid]) l = mid;
            else r = mid - 1;
        }
         
        ll left = x - use[r];
        ll res = cnt[r] + left / w[r];
        ll sum = use[r] + left / w[r] * w[r];
        cout << sum << " " << res << "\n";
    }
}
 
 
 
int main()
{
    IOS;
    solve();   
    return 0;
}

D题

思路:
显然每次反转,最多能够使得对数 +2,那么什么情况才能使得对数 +2?
显然只有连续相同数字和连续相同的数字(且交换的数字不同)交换,才能使得对数 +2
当k比较大,不能使得对数 +2之后,再看有多少能使得对数 +1.
而使得对数 +1的情况,显然是其它单独的数字插入到当前最后一种连续的数字里面来
但是最后还得特判一下答案,这个点有点阴间
比如我自己造的样例
10 10
1 1 1 2 2 1 1 1 1 1
这个答案应该是4,用上述方法得到答案9,因为其它单独的数字不足以插入到剩余的连续数字里面,因此此时答案达到特殊情况上限,即(n - 最多的数字) * 2

int a[N], res;

void solve()
{
    int n, k;
    cin >> n >> k;
    for(int i = 1 ; i <= n ; i ++) cin >> a[i];
     
    vector<int> cnt(n + 1, 0);
    for(int i = 1 ; i < n ; i ++)
        if(a[i] == a[i + 1]) ++ cnt[a[i]];
        else res ++;
     
    multiset<int> st(cnt.begin(), cnt.end());
    st.erase(0);
     
    while(st.size() > 1 && k)
    {
        int x = *st.begin();
        int y = *prev(st.end());
        st.erase(st.begin());
        st.erase(prev(st.end()));
        -- x, -- y, -- k, res += 2;
        if(x) st.insert(x);
        if(y) st.insert(y);
    }
    if(k) res += min(*st.begin(), k);
     
    cnt = vector<int> (n + 1, 0);
    for(int i = 1 ; i <= n ; i ++) cnt[a[i]] ++;
    sort(cnt.rbegin(), cnt.rend());
    res = min(res, (n - cnt[0]) * 2);
    cout << res << endl;
}

int main()
{
    IOS;
    solve();      
    return 0;
}
posted @ 2021-07-10 11:16  beatlesss  阅读(114)  评论(0编辑  收藏  举报