HBU 2023 Simple problem set

目录

7-1 递推公式

7-2 存钱罐

7-3 买东西

7-4 双重子串

7-5 放小球

7-6 最短路径

7-7 统计子序列的个数

7-8 摆放灯笼

7-9 选零食

7-10 1还是2

7-11 最少的门禁数量

7-12 青春猪头之开学了要好好学习

7-13 青春猪头之毕设真头大

7-14 青春猪头之我没学过C语言

7-15 发射小球

7-16 吉利的数字

7-17 买木棒

7-18 切割木棒

7-19 幸运数字

7-20 增加硬币

7-21 三分球

7-22 老虎机

7-23 正交性

7-24 删除不喜欢的数字

7-25 锦标赛


7-1 递推公式

 思路:

由于数据范围很大,模拟会TLE,考虑用矩阵运算优化这个类似斐波那契的递归式,再使用快速幂运算优化矩阵乘法,就能在要求的时间范围内通过了。

AC代码:

#include 
#include 
using namespace std;
typedef long long ll;

const int maxn = 105;
const int P = 1000000007;

struct matrix {
    ll m[maxn][maxn];
};
matrix a;

ll n, k;

matrix matrix_multi(matrix a, matrix b) {
    matrix ans;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            ans.m[i][j] = 0;
            for (int k = 1; k <= n; k++) {
                ans.m[i][j] = (ans.m[i][j] % P + (a.m[i][k] * b.m[k][j]) % P) % P;
            }
        }
    }
    return ans;
}

matrix quick_matrix_pow(matrix a, ll t) {
    matrix ans;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) ans.m[i][j] = 1;
            else        ans.m[i][j] = 0;
        }
    }
    while (t > 0) {
        if (t & 1)
            ans = matrix_multi(a, ans);
        a = matrix_multi(a, a);
        t >>= 1;
    }
    return ans;
}

int main() {
    n = 3;
    cin >> k;
    a.m[1][1] = a.m[1][2] = a.m[1][3] = a.m[2][1] = a.m[3][2] = 1;
    matrix ans = quick_matrix_pow(a, k - 2);
    cout << ((ans.m[1][1] + ans.m[1][2])%P + ans.m[1][3])%P;
    return 0;
}

7-2 存钱罐

思路:

二分,等差数列

代码:

#include 
using namespace std;
typedef long long LL;
int T,n;
int main() {
	cin >> T;
	while (T--) {
		scanf("%d", &n);
		n *= 2;
		int l = 1, r = 1e5 + 10;
		while (l < r) {
			int mid = (l + r) >> 1;
			if ((LL)mid * (mid + 1) >= n)r = mid;
			else l = mid + 1;
		}
		printf("%d\n", l);
	}
}

7-3 买东西

思路:

贪心,由于每张券可以使物品少花一半的钱,那么给当前最大的物品使用券,一定是最优惠的,所以用一个优先队列(堆)维护当前最贵物品,每次用券,将最贵物品价格除以2,放入堆中,直至券全部用完。

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 100010, M = N * 2, mod = 1e9 + 7;
int n, m;
priority_queue q;

int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        q.push(x);
    }
    while (m--) {
        int x = q.top();
        if (x == 0)break;
        q.pop();
        q.push(x / 2);
    }
    LL sum = 0;
    while (!q.empty()) {
        sum += q.top();
        q.pop();
    }
    cout << sum;
}

7-4 双重子串

思路:

dp   我们以dp[i][j] 表示以i为开头的字串和以j为开头的字串最长重复部分的长度。

代码:
 

#include 
using namespace std;
typedef long long LL;
typedef pair PII;
const int N = 5010;
int f[N][N];
int n;
int res;
string s;
int dfs(int x,int y){
    if(x > n || y > n)return 0;
    if(f[x][y] != -1)return f[x][y];
    if(s[x] != s[y])f[x][y] = 0;
    else{
        f[x][y] = min(y - x, dfs(x+1,y+1) + 1);
    }
    return f[x][y];
}

int main() {
	cin >> n;
	cin >> s;
    s = ' ' + s;
    memset(f,-1,sizeof f);
    for(int i=1;i<=n;i++){
        for(int j = i + 1;j <= n;j++){
            res = max(res,dfs(i,j));
        }
    }
    cout<

7-5 放小球

题目有一点点问题:

“对于每个i(1≤i≤n),满足编号为i的倍数的箱子内装的小球的总数等于ai​%2。”

应改为

“对于每个i(1≤i≤n),满足编号为i的倍数的箱子内装的小球的总数%2等于ai​。”

思路:

这道题其实不存在无解的情况,我们总能根据他的条件构造出来一个正解

原因:

我们从n遍历到1, 统计i的倍数放的小球的个数sum,如果sum%2符合要求,那么这个位置就不放小球,如果不符合要求,就在此位置放置小球即可。

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 200010, M = N * 2, mod = 1e9 + 7;
int n, m;
int a[N],b[N];
vector res;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = n; i >= 1; i--) {
        int sum = 0;
        for (int k = 2; i * k <= n; k++)sum += b[i * k];
        if (sum % 2 == 0) {
            if (a[i]) {
                res.push_back(i);
                b[i] = 1;
            }
        }
        else {
            if(!a[i]) {
                res.push_back(i);
                b[i] = 1;
            }
        }
    }
    cout << res.size() << endl;
    for (int i = 0; i < res.size(); i++) {
        cout << res[i] << " ";
    }
}

7-6 最短路径

思路:

此题点与点之间的距离是1,可以考虑bfs,只不过每次都要走3步,因此我们的st数组和d数组都应该是模3意义下的。

代码:

#include 
using namespace std;
typedef long long LL;
typedef pair PII;
const int N = 1e5 + 10;
int n, m, p;
int h[N], e[N], ne[N], idx;
void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int a, b, x, y;
bool st[N][3];
int d[N][3];

int main() {
	scanf("%d%d", &n, &m);
	memset(h, -1, sizeof h);
	memset(st, 0, sizeof st);
	memset(d, -1, sizeof d);
	while (m--) {
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	scanf("%d%d", &x, &y);

	st[x][0] = true;
	d[x][0] = 0;

	queue q;
	q.push({ x,d[x][0] });
	
	while (q.size()) {
		PII t = q.front();
		q.pop();

		int idx = t.first;
		int dist = t.second + 1;
		int fg = dist % 3;
		for (int i = h[idx]; i != -1; i = ne[i]) {
			int j = e[i];
			if (st[j][fg])continue;
			st[j][fg] = true;
			d[j][fg] = dist;
			if (j == y && fg == 0) {
				cout << d[j][0] / 3;
				return 0;
			}
			q.push({j,d[j][fg]});
		}
	}
	cout << "-1";
	return 0;
}

7-7 统计子序列的个数

思路:

dp ,我们用dp[i][j] 来表示a数组中i位置之前,b数组中j位置之前的相同子序列的个数。

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 2010, M = 1e6+10, mod = 1e9 + 7;
int n, m;
LL a[N], b[N];
LL f[N][N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)cin >> a[i];
    for (int j = 1; j <= m; j++)cin >> b[j];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            f[i][j] = (f[i][j - 1] + f[i - 1][j] - f[i - 1][j - 1]) % mod;
            if (a[i] == b[j])f[i][j] += (f[i - 1][j - 1] + 1) % mod;
            if (f[i][j] < 0)f[i][j] += mod;
            if (f[i][j] > mod)f[i][j] %= mod;
        }
    }
    cout << f[n][m] + 1;
    return 0;
}

7-8 摆放灯笼

思路:

模拟即可。

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 1e4+10, M = 1e6+10, mod = 1e9 + 7;
int n, m;
char g[N][N];
int tran[N][N], dir[N][N];
int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)cin >> g[i];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) {
            if (j - 1 < 0 || g[i][j - 1] == '#') {
                int res = 0;
                int k = j;
                while (g[i][k] == '.')k++,res++;
                tran[i][j] = res;
            }
            else tran[i][j] = tran[i][j - 1];
        }
    for (int j = 0; j < m; j++)
        for (int i = 0; i < n; i++) {
            if (i - 1 < 0 || g[i - 1][j] == '#') {
                int res = 0;
                int k = i;
                while (g[k][j] == '.')k++, res++;
                dir[i][j] = res;
            }
            else dir[i][j] = dir[i - 1][j];
        }
    int res = 0;
    for(int i=0;i

7-9 选零食

思路:

模拟即可,枚举取几个零食,放回几个零食

代码:

#include 
using namespace std;
typedef long long LL;
typedef pair PII;
const int N = 55;

int n, m;
int a[N];
int res;
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	int buy = min(n, m);
	for (int i = 1; i <= buy; i++) {
		int put = m - i;
		for (int jl = 0; jl <= i; jl++) {
			int sum = 0;
			priority_queue, greater> q;
			int jr = n - i + jl + 1;
			for (int j = 1; j <= jl; j++) {
				q.push(a[j]);
				sum += a[j];
			}
			for (int j = jr; j <= n; j++) {
				q.push(a[j]);
				sum += a[j];
			}
			int x = put;
			while (!q.empty() && q.top() < 0 && x > 0) {
				sum -= q.top();
				q.pop();
				x--;
			}
			res = max(res, sum);
		}
	}
	cout << res;
	return 0;
}

7-10 1还是2

思路:

考虑一件事情:如果我们得到了这样的线索:“1 和 2 、2 和3 、3和4”,我们只要知道1,就可以把2、3、4推断出来了。因此,判断线索将哪些卡片联系起来,成为了一组,每组中我们只要知道其中一个,其余的就都可以推断出来了。

使用并查集

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 1e5+10, M = 1e6+10, mod = 1e9 + 7;
int n, m;
int p[N];

int find(int x) {
    if (x != p[x])p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)p[i] = i;
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        a = find(a), b = find(b);
        if (a != b)p[a] = b;
    }
    set st;
    for (int i = 1; i <= n; i++) {
        p[i] = find(p[i]);
        st.insert(p[i]);
    }
    cout << st.size();
    return 0;
}

7-11 最少的门禁数量

思路:

典型差分,将所有大门的信息差分,求一遍前缀和,当前和等于门的数量的钥匙即满足要求

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 1e5+10, M = 1e6+10, mod = 1e9 + 7;
int n, m;
int chafen[N];

int main()
{
    cin >> n >> m;
    int men = m;
    while (m--) {
        int l, r;
        cin >> l >> r;
        chafen[l] += 1;
        chafen[r + 1] -= 1;
    }
    int res = 0;
    for (int i = 1; i <= n; i++) {
        chafen[i] += chafen[i - 1];
        if (chafen[i] == men)res++;
    }
    cout << res;
    return 0;
}

7-12 青春猪头之开学了要好好学习

思路:

由于我们可以进行无限次操作,所以如果负数个数为偶数的话,我们总能将其全变为正数。

但如果负数个数为奇数,我们不管怎么翻转,最后都会留下一个负数,那我们一定会留最小的那个负数。

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 1e5+10, M = 1e6+10, mod = 1e9 + 7;
int n, m;
vector a;
int main()
{
    LL res = 0;
    cin >> n;
    for (int i = 0; i < n; i++){
        LL x;
        cin >> x;
        if (x < 0)m++;
        a.push_back(x);
    }
    if (m % 2 == 0) {
        for (int i = 0; i < n; i++)res += abs(a[i]);
    }
    else {
        for (int i = 0; i < n; i++) {
            a[i] = abs(a[i]);
        }
        sort(a.begin(), a.end());
        for (int i = 0; i < n; i++) {
            if (!i)res += (-1) * a[i];
            else res += a[i];
        }
    }
    cout << res;
    return 0;
}

7-13 青春猪头之毕设真头大

思路:(双指针)

枚举每一个0序列区间,判断将其全变成1,和两边的1相连长度是多少,更新最大值

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 1e5+10, M = 1e6+10, mod = 1e9 + 7;
int n, m;
string s;
int main()
{
    cin >> n >> m;
    getchar();
    cin >> s;
    int l = 0, r = 0, cnt = 0, num = 0;
    while (r < n) {
        if (s[r] == '0') {
            while (s[r] == '0'&& r < n - 1)r++;
            cnt++;
        }
        while (cnt > m) {
            while (s[l] == '1' && l < n - 1)l++;
            while (s[l] != '1' && l < n - 1)l++;
            cnt--;
        }
        num = max(num, r - l + 1);
        r++;
    }
    cout << num;
    return 0;
}

7-14 青春猪头之我没学过C语言

思路:

从1到n的异或值有规律,可以打表观察。

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 1e5+10, M = 1e6+10, mod = 1e9 + 7;
int n;
LL x, y;
LL fun(LL x) {
    if (x % 4 == 1)return 1;
    else if (x % 4 == 2)return x + 1;
    else if (x % 4 == 3)return 0;
    else return x;
}
int main()
{
    cin >> x >> y;
    LL res = fun(max((LL)0,x - 1)) ^ fun(y);
    cout << res;
    
    return 0;
}

7-15 发射小球

思路:

推公式即可

代码:

#include 
using namespace std;
typedef long long LL;
int main()
{
    double sx, sy, gx, gy;
    double k = 0;
    scanf("%lf%lf%lf%lf", &sx, &sy, &gx, &gy);
    gy = -gy;
    printf("%.10lf", sx - (double)(sx-gx)/(sy-gy)*sy);
    return 0;
}

7-16 吉利的数字

思路:

只需要判断所有出现过的数字能组成的3位数是否是8的倍数即可。

代码:

#include 
using namespace std;
typedef pair PII;
typedef long long LL;
const int N = 2e5 + 10;
int st[11];
char s[N];
int main()
{
	cin >> s;
	if (strlen(s) == 1) {
		if ((s[0] - '0') % 8 == 0)cout << "Yes";
		else cout << "No";
		return 0;
	}
	if (strlen(s) == 2) {
		int t = 0, x = 0;
		t += s[0] - '0';
		t *= 10;
		t += s[1] - '0';
		x += s[1] - '0';
		x *= 10;
		x += s[0] - '0';
		if (t % 8 == 0 || x % 8 == 0)cout << "Yes";
		else cout << "No";
		return 0;
	}
	for (int i = 0; i < strlen(s); i++) {
		int x = s[i] - '0';
		st[x]++;
	}

	for (int i = 1; i < 10; i++)
		for (int j = 1; j < 10; j++)
			for (int k = 1; k < 10; k++) {
				int t = 0;
				bool fg1 = false, fg2 = false, fg3 = false;
				if (st[i]) {
					t += i;
					st[i]--;
					fg1 = true;
				}else continue;
				if (st[j]) {
					t *= 10;
					t += j;
					st[j]--;
					fg2 = true;
				}else{ 
                    st[i]++;
                    continue;
                }
				if (st[k]) {
					t *= 10;
					t += k;
					st[k]--;
					fg3 = true;
				}else{ 
                    st[i]++;
                    st[j]++;
                    continue;
                }
				if (fg1)st[i]++;
				if (fg2)st[j]++;
				if (fg3)st[k]++;
				if (t && t % 8 == 0) {
					cout << "Yes";
					return 0;
				}
			}
	cout << "No";
	return 0;
}

7-17 买木棒

思路:

贪心,肯定要购买长度为n+1的木棒,考虑其能最多分割成多少个小的木棒,其余的购买。

代码:

#include 
using namespace std;
typedef long long LL;
LL S, P;
int main()
{
    cin >> S;
    P = S;
    S++;
    int i = 1;
    while (S >= i) {
        S -= i;
        i++;
    }
    cout << P - i + 2;
    return 0;
}


7-18 切割木棒

思路:

长度为n的木棒,找11个切割位置:C_{11}^{n}

代码:

#include 
using namespace std;
typedef long long LL;
const int N = 210;
LL f[N][13];
int main()
{
    for (int i = 0; i < N; i++) {
        for (int j = 0; j <= i && j < 13; j++) {
            if (j == 0)f[i][j] = (LL)1;
            else f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
        }
    }
    int n; cin >> n;
    cout << f[n - 1][11];
    return 0;
}

7-19 幸运数字

思路:

预处理:线性筛法求素数,求其前缀和

代码:

#include 
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int prime[N],cnt;
int s[N];
bool st[N];
void get_prime(int n){
    for(int i=2;i<=n;i++){
        if(!st[i]){
            prime[cnt++]=i;
        }
        for(int j=0;prime[j]<=n/i;j++){
            st[prime[j]*i]=true;
            if(i%prime[j]==0)break;
        }
    }
}

int main()
{
    get_prime(N-1);
    for(int i=2;i>q;
    while(q--){
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",s[r]-s[l-1]);
    }
    return 0;
}

7-20 增加硬币

思路:

期望DP

HBU 2023 Simple problem set_第1张图片

来源: AT_abc184_d [ABC184D] increment of coins - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

代码:

#include 
using namespace std;
typedef long long LL;
const int N = 110;
double f[N][N][N];
int d = 0;

double dp(int a, int b, int c) {
    if (f[a][b][c] >= 0)return f[a][b][c];
    f[a][b][c] = 0;
    if (a >= 100 || b >= 100 || c >= 100)return f[a][b][c];
    int sum = a + b + c;
    f[a][b][c] += (dp(a + 1, b, c) + 1) * (1.0 * a) / sum;
    f[a][b][c] += (dp(a, b + 1, c) + 1) * (1.0 * b) / sum;
    f[a][b][c] += (dp(a, b, c + 1) + 1) * (1.0 * c) / sum;
    return f[a][b][c];
}
int main()
{
    int a, b, c;
    scanf("%d%d%d", &a, &b, &c);
    memset(f, -1, sizeof f);
    printf("%.9lf", dp(a, b, c));

    return 0;
}

7-21 三分球

自己写吧

7-22 老虎机

同上

7-23 正交性

模拟

代码:

#include 
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        a[i]*=x;
        a[i]+=a[i-1];
    }
    
    if(a[n]==0)cout<<"Yes";
    else cout<<"No";
    return 0;
}

7-24 删除不喜欢的数字

模拟

代码:

#include 
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        if(x==m)continue;
        printf("%d ",x);
    }
    return 0;
}

7-25 锦标赛

#include 
using namespace std;
typedef long long LL;
const int N = 20;
int n;
int res1,res2;
int d1,d2;
int main()
{
    cin>>n;
    n = 1<res2)cout<

你可能感兴趣的:(算法,c++)