2023ICPC山东省赛补题题解

2023ICPC山东省赛补题题解

写在前面

​ 2023ICPC山东省赛大概情况是 7 7 7 题以上金, 然后 5 5 5 题快和 6 6 6 题是银,3题快、4题和5题慢都是铜,题目旁标注的是在这场省赛中题目是什么牌子的题。

​ 然后就是题解用于个人复习与复盘,题解顺序按难度从易到难(对于个人),部分题目只提供代码,部分题( B 、 E B、E BE )提供代码和详细解题思路、实现细节及赛时总结,python代码由本人实现,c++代码参考队友实现或 S U A SUA SUA W i k i Wiki Wiki 给出的代码。

Problem - I - Three Dice(签到)

代码(提供C++)

C++ Code

#include<bits/stdc++.h>
using namespace std;

bool f;
int a,b;
void dfs(int deep,int l,int r)
{
	if(f)
		return;
	if(deep==3){
		if(l==a&&r==b)
			f=1;
		return;
	}
	for(int i=1;i<=6;++i){
		if(i==1||i==4)
			dfs(deep+1,l+i,r);
		else
			dfs(deep+1,l,r+i);
	} 
}
void solve()
{
	cin>>a>>b;
	dfs(0,0,0);
	if(f)
		cout<<"Yes\n";
	else
		cout<<"No\n"; 
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t=1;
//	cin>>t;
	while(t--)
		solve();
	return 0;
} 

Problem - A - Codeforces(排序模拟)

代码(提供python 和C++)

python code

import sys
input = sys.stdin.readline

def solve():
    n,k = map(int, input().split())
    ans = 0
    dic = {}
    for _ in range(n):
        x,y = map(int, input().split())
        if x in dic: dic[x] += y
        else: dic[x] = y
    ls = sorted(dic.keys())
    su = [dic[ls[0]]]
    for i in range(1,len(ls)):
        su.append(su[-1]+dic[ls[i]])
    for i in range(len(ls)):
        tmp = ls[i]*k
        if tmp < su[i]:
            print("No")
            return
    print("Yes")

for _ in range(int(input())):
    solve()

c++ code(参考SUA Wiki)

#include <bits/stdc++.h>
#define MAXN 100
using namespace std;
typedef pair<int, int> pii;

int n;
long long K;
pii A[MAXN + 10];

void solve() {
    scanf("%d%lld", &n, &K);
    for (int i = 1; i <= n; i++) scanf("%d%d", &A[i].first, &A[i].second);
    // 订单按哪天交付排序
    sort(A + 1, A + n + 1);

    int last = 0;
    long long have = 0;
    for (int i = 1; i <= n; i++) {
        // 计算增加的存货量
        have += (A[i].first - last) * K;
        // 判断存货是否足够
        if (have >= A[i].second) have -= A[i].second;
        else { printf("No\n"); return; }
        last = A[i].first;
    }
    printf("Yes\n");
}

int main() {
    int tcase; scanf("%d", &tcase);
    while (tcase--) solve();
    return 0;
}

Problem - G - Matching(思维+排序)

代码(提供C++)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
vector<int> a[500005];
map<int,int> idx;
int cnt=1;
void solve()
{
	ll ans=0;
	cin>>n; 
	for(int i=1;i<=n;++i){
		int x;
		cin>>x;
		int tmp=x-i;
		if(!idx[tmp])
			idx[tmp]=cnt++;
		a[idx[tmp]].push_back(x);
	}
	for(int i=0;i<cnt;++i){
		if(a[i].size()==1)
			continue;
		sort(a[i].begin(),a[i].end());
		for(int j=a[i].size()-1;j>=0;j-=2){
			if(j-1<0){
//				cout<<1<<'\n';
				break;
			}
			int tmp=a[i][j]+a[i][j-1];
			if(tmp>0)
				ans+=tmp;
		}
		a[i].clear();	
	}
	cout<<ans<<'\n';
	idx.clear(); 
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t=1;
	cin>>t;
	while(t--)
		solve();
	return 0;
} 

Problem - D - Fast and Fat(铜牌题,二分)

代码(提供C++)

#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int,int> pii;
const int M=1e5+5;
int n;
vector<pii> q(M);
vector<pii> p(M);
bool cmp(pii a,pii b){
	return a.first+a.second<b.first+b.second;
}
bool check(int d){
	int idx=n;
	vector<int> vis;
	for(int i=n;i>=1;i--){
		if(q[i].first>=d) vis.push_back(q[i].first+q[i].second);
	}
	idx=0;
	for(int i=n;i>=1;i--){
		if(p[i].second<d){
			bool f=0;
			for(;idx<vis.size();idx++){
				if(vis[idx]-p[i].first>=d){
					f=1;
					idx++;
					break;
				} 
			}
			if(!f) return 0;
		}
	}
	return 1;
}
void solve(){	
	cin>>n;	
	int l=1e9+5,r=-1;
	int ans=1e9+5;
	for(int i=1;i<=n;i++){
		cin>>q[i].first>>q[i].second;
		p[i].first=q[i].second;
		p[i].second=q[i].first;
		r=max(r,q[i].first);
		l=min(l,q[i].first);
	}
	ans=l;
	sort(q.begin()+1,q.begin()+n+1,cmp);
	sort(p.begin()+1,p.begin()+n+1);	
	//cout<<p[n].first<<'\n';
	while(l<=r){
		int mid=l+(r-l)/2;
		if(check(mid)){
			ans=mid;
			l=mid+1;
		}
		else{
			r=mid-1;
		}
	}
	cout<<ans<<'\n';
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int ins;
	cin>>ins;
	while(ins--) solve();
	return 0;
}

Problem - L - Puzzle: Sashigane (铜牌题,构造)

代码(提供python 和C++)

python code

import sys
input = sys.stdin.readline

def solve():
    n,a,b = map(int, input().split())
    print("Yes")
    ans = []
    now = [[a,b] for _ in range(4)]
    nx = [1,1,-1,-1]
    ny = [1,-1,-1,1]

    le = 0
    while le < n-1:
        for i in range(4):
            xx,yy = now[i]
            x = xx + nx[i]
            y = yy + ny[i]
            if x < 1 or x > n or y < 1 or y > n: continue
            le += 1
            ans.append((x,y,-le*nx[i],-le*ny[i]))
            now[i] = [x,y]
            if i == 0 or i == 2:
                now[(i+1)%4] = [x,y-le*ny[i]]
                now[i-1] = [x-le*nx[i],y]
            else:
                now[(i + 1) % 4] = [x-le*nx[i], y]
                now[i - 1] = [x, y-le*ny[i]]
            break
    print(len(ans))
    for i in range(len(ans)):
        print(*ans[i])

solve()

c++ code(参考 SUA Wiki)

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, i, j; scanf("%d%d%d", &n, &i, &j);
    // UDLR 表示现在包出来的正方形的上下左右边界
    int U = i, D = i;
    int L = j, R = j;
    printf("Yes\n%d\n", n - 1);
    for (int k = 1; k < n; k++) {
        if (U > 1 && L > 1) {
            U--; L--;
            printf("%d %d %d %d\n", U, L, D - U, R - L);
        } else if (U > 1 && R < n) {
            U--; R++;
            printf("%d %d %d %d\n", U, R, D - U, L - R);
        } else if (D < n && L > 1) {
            D++; L--;
            printf("%d %d %d %d\n", D, L, U - D, R - L);
        } else {
            D++; R++;
            printf("%d %d %d %d\n", D, R, U - D, L - R);
        }
    }
    return 0;
}

Problem - E - Math Problem(银牌题,K进制)

题目分析与求解

​ 首先一定是先进行除法,然后再进行乘法,不会把新乘上去的 东西再除掉。由于题目给定 0 0 0 是任意数的倍数,所以我们最多除 O ( log ⁡ n ) O(\log{n}) O(logn) 次。

​ 我们考虑乘法的话怎么让 n n n 变为 m m m 的倍数呢,我们把多次乘法的式子拆开来看,假设乘了 a a a 次: n = k a n + k a − 1 x 1 + k a − 2 x 2 . . . + x a n = k^an+k^{a-1}x_1+k^{a-2}x_2...+x_a n=kan+ka1x1+ka2x2...+xa,根据式子,我们很容易看出在 k a n k^an kan 的后面就是一个 k k k 进制数的表示,可以组成小于等于 k a − 1 k^a-1 ka1 的任意数,所以我们只需要计算在 k a n % m k^an\%m kan%m 的余数是否小于 k a − 1 k^a-1 ka1 即可。

代码(提供python 和C++)

Python Code

import sys
input = sys.stdin.readline

def solve():
    n,k,m,a,b = map(int, input().split())
    if n % m == 0:
        print(0)
        return
    if k == 1:
        print(-1)
        return
    ls = [1]
    while ls[-1] <= 2e18: ls.append(ls[-1]*k)

    idx = 0
    for i in range(1,len(ls)):
        if ls[i] > n:
            idx = i
            break
    ans = 1e18
    for i in range(idx):
        if i != 0: n //= k
        for j in range(len(ls)):
            if (m - ((n*ls[j]) % m))%m <= ls[j]-1:
                ans = min(ans, b*i+a*j)
                break
    ans = min(ans, b*idx)
    print(ans)

for _ in range(int(input())):
    solve()

C++ Code

E - 数学问题 - SUA Wiki(代码来源)

#include <bits/stdc++.h>
using namespace std;

long long n, K, m, a, b;

void solve() {
    scanf("%lld%lld%lld%lld%lld", &n, &K, &m, &a, &b);
    if (n % m == 0) { printf("0\n"); return; }
    if (K == 1) { printf("-1\n"); return; }

    long long ans = 1e18, cost = 0;
    while (true) {
        // base:乘法操作之后的范围区间左端点
        // p:乘法操作之后范围区间的长度
        long long base = n % m, p = 1;
        for (int i = 0; ; i++) {
            // 距离下一个 m 的倍数还有多少
            long long delta = (m - base) % m;
            if (delta < p) {
                // 范围区间覆盖了 m 的倍数,更新答案
                ans = min(ans, cost + i * a);
                break;
            }
            // 再做一次乘法操作
            base = base * K % m;
            p *= K;
        }
        if (n == 0) break;
        // 枚举除法操作的次数
        n /= K;
        cost += b;
    }
    printf("%lld\n", ans);
}

int main() {
    int tcase; scanf("%d", &tcase);
    while (tcase--) solve();
    return 0;
}

Problem - B - Building Company(金牌题,拓扑排序思想+堆)

题目分析与求解

​ 在vp训练的时候,其实和队友感觉就挺像拓扑排序的,由于没想清楚怎么建图,即怎么连边, d e g r e e degree degree含义等没出来,我做完第6题后感觉脑子有点迟钝了剩下一个小时也每想出来这题的具体实现。

​ 本题类似拓扑排序的思维,我们维护每项工程还有几条要求没有满足( d e g r e e degree degree),并将不同工人种类的要求分别维护(左集合的点,只有出边)。所有要求都被满足的工程将加入队列( d e g r e e = 0 degree=0 degree=0)。从队列中取出一项工程后,我们会获得该工程的奖励。当工种为 t t t 的员工加入公司后,我们检查和工种 t t t 有关的人数最少的未满足需求。如果这项需求被满足了,则对应工程的要求数减一( d e g r e e − 1 degree-1 degree1)。要求数减少为零的工程继续加入队列。

关于实现细节

​ 注意看上面加粗的检查和工种 t t t 有关的人数最少的未满足需求,这里我们需要先排序或使用堆实现,而拓扑排序是 O ( ∑ i = 1 n k i ) O(\sum_{i=1}^n{k_i}) O(i=1nki) 的,对于每项工程的要求处理由于用了堆,复杂度是 O ( ∑ i = 1 n m i log ⁡ ( ∑ i = 1 n m i ) ) O(\sum_{i=1}^n{m_i}\log{(\sum_{i=1}^n{m_i})}) O(i=1nmilog(i=1nmi))

​ 当然,如果不用堆维护会怎么样呢?对于每一个工程完成后,我们需要扫描其 b o n u s bonus bonus 中的所有工种,来判断是否有新的工程满足条件,时间复杂度是 O ( ∑ i = 1 n k i ∑ i = 1 n m i ) O(\sum_{i=1}^n{k_i}\sum_{i=1}^n{m_i}) O(i=1nkii=1nmi),明显超时。

代码(提供python 和C++)

Python Code

import sys
from collections import deque
from heapq import heappop, heappush
input = sys.stdin.readline

def solve():
    """工人种类为左集合点,工程为右集合点,组成一个二分图"""
    dic = {}
    ls = list(map(int, input().split()))
    for i in range(1,2*ls[0],2):
        dic[ls[i]] = ls[i+1]  # 一开始所拥有的员工
    n = int(input())
    deg = [0]*(n+1)
    bonus = [{} for _ in range(n+1)]
    g = {}
    for i in range(1,n+1):
        ls = list(map(int, input().split())) # 工程 i 的要求
        for j in range(1,ls[0]*2,2):
            if ls[j] in dic and dic[ls[j]] >= ls[j+1]: continue  # 该要求已满足
            deg[i] += 1  # 每一个工程未被满足的要求向工程连一条边,对应工程的 degree += 1
            if ls[j] in g:
                heappush(g[ls[j]], (ls[j+1], i))
            else:
                g[ls[j]] = []
                heappush(g[ls[j]], (ls[j+1], i))

        ls = list(map(int, input().split()))
        for j in range(1, ls[0] * 2, 2):  # 工程 i 的 bonus
            bonus[i][ls[j]] = ls[j+1]

    ans = 0
    q = deque()
    for i in range(1,n+1):
        if deg[i] == 0: q.append(i)
    while q:
        idx = q.popleft()  # 项目 idx 被完成,通过bonus更新要求
        ans += 1
        for x,val in bonus[idx].items():
            dic[x] = dic.get(x,0)+val
            if x not in g: continue
            while g[x]:
                val,i = heappop(g[x])
                if val > dic[x]:
                    heappush(g[x], (val,i))
                    break
                deg[i] -= 1
                if deg[i] == 0: q.append(i)
    print(ans)

solve()

C++ Code

B - 建筑公司 - SUA Wiki(该c++代码来源)

#include <bits/stdc++.h>
#define MAXN ((int) 1e5)
using namespace std;
typedef pair<int, int> pii;

int g, n, A[MAXN + 10][2];

// M[i] 表示第 i 项工程还有几条要求未满足
int M[MAXN + 10];
// B[i] 表示第 i 项工程的奖励
vector<pii> B[MAXN + 10];

// mp[i] 是一个按人数排序的小根堆,维护了与工种 i 有关的未满足需求
unordered_map<int, priority_queue<pii, vector<pii>, greater<pii>>> mp;
// have[i] 表示公司现有工种 i 的员工数量
unordered_map<int, long long> have;
queue<int> q;

// 公司增加工种 t 的员工共 u 名
void add(int t, int u) {
    long long &val = have[t];
    val += u;
    priority_queue<pii, vector<pii>, greater<pii>> &pq = mp[t];
    // 看哪些和工种 t 有关的需求被满足了
    while (!pq.empty()) {
        pii p = pq.top();
        if (p.first > val) break;
        pq.pop();
        // 工程所有要求都被满足,加入队列
        if ((--M[p.second]) == 0) q.push(p.second);
    }
}

int main() {
    scanf("%d", &g); assert(g >= 1);
    for (int i = 1; i <= g; i++) scanf("%d%d", &A[i][0], &A[i][1]);
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &M[i]);
        for (int j = 1; j <= M[i]; j++) {
            int a, b; scanf("%d%d", &a, &b);
            mp[a].push(pii(b, i));
        }
        int K; scanf("%d", &K);
        for (int j = 1; j <= K; j++) {
            int c, d; scanf("%d%d", &c, &d);
            B[i].push_back(pii(c, d));
        }
    }
    // 把没有任何要求的工程加入队列
    for (int i = 1; i <= n; i++) if (M[i] == 0) q.push(i);
    // 公司一开始就有的员工
    for (int i = 1; i <= g; i++) add(A[i][0], A[i][1]);

    int ans = 0;
    // 类似拓扑排序,不断从队列中拿出工程,并获得奖励
    while (!q.empty()) {
        int idx = q.front(); q.pop();
        ans++;
        for (pii p : B[idx]) add(p.first, p.second);
    }
    printf("%d\n", ans);
    return 0;
}
  • 46
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FarawayTraveler_Yuch

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值