2023ICPC山东省赛补题题解
文章目录
- 2023ICPC山东省赛补题题解
- 写在前面
- [Problem - I - Three Dice](https://codeforces.com/gym/104417/problem/I)(签到)
- [Problem - A - Codeforces](https://codeforces.com/gym/104417/problem/A)(排序模拟)
- [Problem - G - Matching](https://codeforces.com/gym/104417/problem/G)(思维+排序)
- [Problem - D - Fast and Fat](https://codeforces.com/gym/104417/problem/D)(铜牌题,二分)
- [Problem - L - Puzzle: Sashigane](https://codeforces.com/gym/104417/problem/L) (铜牌题,构造)
- [Problem - E - Math Problem](https://codeforces.com/gym/104417/problem/E)(银牌题,K进制)
- [Problem - B - Building Company](https://codeforces.com/gym/104417/problem/B)(金牌题,拓扑排序思想+堆)
写在前面
2023ICPC山东省赛大概情况是 7 7 7 题以上金, 然后 5 5 5 题快和 6 6 6 题是银,3题快、4题和5题慢都是铜,题目旁标注的是在这场省赛中题目是什么牌子的题。
然后就是题解用于个人复习与复盘,题解顺序按难度从易到难(对于个人),部分题目只提供代码,部分题( B 、 E B、E B、E )提供代码和详细解题思路、实现细节及赛时总结,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+ka−1x1+ka−2x2...+xa,根据式子,我们很容易看出在 k a n k^an kan 的后面就是一个 k k k 进制数的表示,可以组成小于等于 k a − 1 k^a-1 ka−1 的任意数,所以我们只需要计算在 k a n % m k^an\%m kan%m 的余数是否小于 k a − 1 k^a-1 ka−1 即可。
代码(提供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 degree−1)。要求数减少为零的工程继续加入队列。
关于实现细节
注意看上面加粗的检查和工种 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=1nki∑i=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;
}