A-小乔与小灰灰

题面:
小乔和小灰灰是好朋友,现在如果一个字符串中同时出现子序列“XiaoQiao”和“XiaoHuiHui”,那么小乔和小灰灰都会感到开心。

输入:
输入包含一行一个字符串S
字符串中仅包含大写字母和小写字母

输出:
如果这个串会让小乔和小灰灰都感到开心,那么输出“Happy”,否则输出“emm”。

样例数据

样例输入1:
XiaoQiaoheHuiHui
样例输出1:
Happy
样例输入2:
Xiaohuihuihexiaoqiao
样例输出2:
emm

签到题,按下标匹配。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define get(x) scanf("%d", &x)
#define set(a,b) memset(a, b, sizeof(a))
#define rep(a, b, c) for (int a = b; a < c; a++)

using namespace std;

const int LIM = 1001;
char x[10] = {'X','i','a','o','Q','i','a','o'};
char h[20] = {'X','i','a','o','H','u','i','H','u','i'};

char s[LIM];

int main()
{
	int k1 = 0;
	int k2 = 0;
	scanf("%s", &s);
	int len = strlen(s);
	rep(i, 0, len)
	{
		if(s[i] == x[k1]) k1++;
		if(s[i] == h[k2]) k2++; 
	}
	if (k1 == 8 && k2 == 10) cout << "Happy";
	else cout << "emm";
    return 0;
}

B-牛能和小镇

题面:
在牛国有n个小镇编号1,2,3…n−1,n。用二维平面来表示每个小镇的位置,第i个小镇的位置为(xi,yi)。牛能做为牛国的国王,决定给小镇之间建设一些道路,以便于任意小镇之间都能相互到达,在第i个小镇和第j个小镇之间建设一条道路的花费如下,请让总花费尽量小。(1<=n,x,y<=1e5)

输入:
第一行输入一个整数n
接下来n行每行两个整数xi,yi​表示第i个小镇的坐标。

输出:
输出一个整数表示最小花费。

样例数据

样例输入
3
1 4
2 8
3 6
样例输出
252 

读体面感觉就很像最小生成树。要计算一颗最小生成树就要得到各边权,将原题公式转化一下可以得到如下形式:

如果设

那么i,j两点间的距离就可以抽象为|Vi-Vj|。算出每个点的V值然后贪心连边就行了。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define get(x) scanf("%d", &x)
#define lget(x) scanf("%lld", &x)
#define set(a,b) memset(a, b, sizeof(a))
#define rep(a, b, c) for (int a = b; a <= c; a++)

using namespace std;

typedef long long ll;

const int LIM = 100001;

int n;
ll x, y, ans;
ll v[LIM];

int main()
{
	get(n);
	rep(i, 1, n) 
	{
		lget(x); lget(y);
		v[i] = x*x*y + y*y*(y - 2*x);
	}
	sort(v+1, v+n+1);
	rep(i, 1, n-1)
		ans += v[i+1] - v[i];
	cout << ans;
	return 0;
} 

赛时这题还是很艰辛的,一眼看出了它是最小生成树,然后就开始硬套板子,死活套不出来。赛后发现可以根据题目特性来贪心,时空都相当优,这个故事告诉我们做题不能囿于模板,题是死的但人应该是活的。

C-装备合成

题面:
牛牛有x件材料a和y件材料b,用2件材料a和3件材料b可以合成一件装备,用4件材料a和1件材料b也可以合成一件装备。牛牛想要最大化合成的装备的数量,于是牛牛找来了你帮忙。

输入:
输入包含t组数据
第一行一个整数t(1<=t<=10000)
接下来t行每行两个整数x,y(1<=x,y<=1e9)

输出:
每组数据输出一个整数作为答案

样例数据

样例输入
5
4 8
7 6
8 10
100 4555
45465 24124
样例输出
2
2
3
50
13917

考虑最简单的情况:假设装备1做了i件,那么装备2最多做min[(x-2i)/4, y-3i]件。此时所求的就是max(i+ min[(x-2i)/4, y-3i])。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define get(x) scanf("%d", &x)
#define lget(x) scanf("%lld", &x)
#define set(a,b) memset(a, b, sizeof(a))
#define rep(a, b, c) for (int a = b; a <= c; a++)

using namespace std;

typedef long long ll;

int t;
ll x, y;

int main()
{
	get(t);
	while(t--)
	{
		ll res = 0;
		lget(x);lget(y);
		rep(i, 1, x)
			if (res < i + min((x-2*i)/4, y-3*i)) 
				res = i + min((x-2*i)/4, y-3*i);
		printf("%lld\n", res);
	}
	return 0;
}

但是问题来了,这题直接枚举会超时,考虑优化算法。

令x=45465, y=24124打表发现答案存在单峰极值,于是可以用三分算法

三分算法是一种针对凸性函数极值问题的算法。在区间[l, r]中令m1=l+(r-l)/3,
m2=r-(r-l)/3,即取⅓与⅔位置的值。如果f(m1)>f(m2),区间变为[l, m2],反之变为[m1, r],即保留较大值所在的区间。重复上述操作直到区间足够小。

图源

那么思路就很明确了,此处的凸性函数就是f(i) = i+ min[(x-2i)/4, y-3i] 。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define get(x) scanf("%d", &x)
#define lget(x) scanf("%lld", &x)
#define set(a,b) memset(a, b, sizeof(a))
#define rep(a, b, c) for (int a = b; a <= c; a++)

using namespace std;

typedef long long ll;

int t;
ll x, y;

int main()
{
	get(t);
	while(t--)
	{
		lget(x);lget(y);
		ll l = 0, r = min(x/2, y/3);
		while(r-l > 15)//此处取一个枚举不超时的区间大小就行
		{
			ll m1 = l+(r-l)/3;
			ll m2 = r-(r-l)/3;
			if (m1 + min((x-2*m1)/4,y-3*m1) > m2 + min((x-2*m2)/4,y-3*m2)) r = m2;
			else l = m1;
		}
		ll res = 0;
		rep(i, l, r) res = max(res, i+min((x-2*i)/4,y-3*i));
		printf("%lld\n", res);
	}
	return 0;
}

D-取石子游戏

题面:
小灰灰和小乔在玩取石子游戏,一堆里有n个石子,小灰灰和小乔轮流操作,小灰灰先手,每次操作的人可以进行以下操作:       
假设当前石子数量为k,如果k>=2,那么将石子分为f(k)和k−f(k)两堆,然后选择其中任意一堆石子取走。否则当前操作的人输。其中f(k)=x,x为满足满足x∗2<=k的最大整数。
小灰灰和小乔都非常聪明,所以都会采用最优的策略,你知道最后小灰灰和小乔谁能赢得游戏吗?

输入描述:
输入共包含t组数据
第一行一个整数t(1<= t <= 100000),表示测试用例的组数
接下来t行每行一个整数n(1<=n<=1e18)。

输出描述:
对于每组案例,如果小灰灰赢,输出“XiaoHuiHui”,否则输出“XiaoQiao”

样例数据

样例输入
10
1
2
3
4
5
6
7
8
9
10
样例输出
XiaoQiao
XiaoHuiHui
XiaoHuiHui
XiaoQiao
XiaoQiao
XiaoQiao
XiaoHuiHui
XiaoHuiHui
XiaoHuiHui
XiaoHuiHui

这题挺有意思的。样例已经把规律告诉你了,真正的难点在1e18的数据范围上。

当n=1时是必败态;而2×1与2×1+1可以通过一次操作转化到n=1,把锅甩给对面,属于必胜态,而 2×2、2×2+1、2 × 3通过1次操作转换n=2或3,属于必败态….

以此类推,胜与败的是作为不断增长的区间来出现的,增长与2的幂次有关。为了在合理时间内快速查询到n的状态,可以先初始化每个胜/败区间的头下标与尾下标,然后对于每个n去二分查找它属于哪一个区间并判胜负。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define get(x) scanf("%d", &x)
#define lget(x) scanf("%lld", &x)
#define set(a,b) memset(a, b, sizeof(a))
#define rep(a, b, c) for (int a = b; a <= c; a++)

using namespace std;

typedef long long ll;

const int LIM = 1000;

int cnt, t;
ll f[LIM], l[LIM];//f为首l为尾

int main()
{
    f[++cnt] = 1; l[cnt] = 1;//初始化败
    f[++cnt] = 2; l[cnt] = 3;//初始化胜
    while(l[cnt] <= 1e18)
    {
        cnt++;
        f[cnt] = l[cnt-1] + 1;
        if(cnt%2 != 0)//败区间
            l[cnt] = l[cnt-1]*2;
        else//胜区间
            l[cnt] = l[cnt-1]*2 + 1;
    }
    get(t);
    while(t--)
    {
        ll n;
		lget(n);
        int p = upper_bound(f+1, f+1+cnt, n)- f - 1;//二分答案
        if(p%2 != 0) printf("XiaoQiao\n");
        else printf("XiaoHuiHui\n");
    }
    return 0;
}

在扒榜的时候还看见一个大佬的代码,时空都很优,可惜我没读明白,这里还是贴一下。

//作者牛客id:zzuacmer
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
 
#define get(x) scanf("%d", &x)
#define lget(x) scanf("%lld", &x)
#define set(a,b) memset(a, b, sizeof(a))
#define rep(a, b, c) for (int a = b; a <= c; a++)
 
using namespace std;
 
typedef long long ll;
  
int t;
ll n;
  
int main()
{
    cin >> t;
    while(t--)
    {
        lget(n);
        ll x = 3;
        while(n > x) x = x*4 + 1;
        if (n > x/2) puts("XiaoHuiHui");
        else puts("XiaoQiao");
    }
    return 0;
}

感觉原理上跟我的方法一样,就是不太看得懂