The 2024 Hunan Multi-School Programming Training Contest, Round 1
写在前面
比赛地址:https://codeforces.com/gym/509646
以下按个人向难度排序。
很基础的一套题,感觉以后结训比赛可以搬几道。
Just a Way 首战被吊打呃呃
A
模拟即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
LL h, k, v, s, ans = 0;
std::cin >> h >> k >> v >> s;
while (h > 0) {
v += s;
v -= std::max(1ll, v / 10ll);
if (v >= k) ++ h;
else if (v > 0) -- h;
else v = 0, h = 0;
if (h <= 0) v = 0;
ans += v;
if (s > 0) -- s;
}
std::cout << ans << "\n";
return 0;
}
B
发现有限制 \(h - l\le 1000\),则直接暴力枚举每一段的长度并枚举每一段检查的复杂度是 \(O((h - l)\times n)\) 级别的,暴力即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,l,h;
const int N=3e4+5;
int a[N],b[N];
int sum(int li,int ri){
return b[ri]-b[li-1];
}
int f[N][2];//0 min 1 max
signed main(){
std::cin>>n>>l>>h;
for(int i=1;i<=n;++i){
cin>>a[i];
b[i]=b[i-1]+a[i];
}
int minn=n*n*n,maxx=0;
for(int i=l;i<=h;++i){
for(int k=1;k<=i;++k){
int tmp=sum(1,k);
int num=0;
if(tmp>0) num++;
for(int j=k+1;j<=n;j+=i){
int r=j+i-1;
r=min(r,n);
tmp=sum(j,r);
if(tmp>0) ++num;
}
minn=min(minn,num);
maxx=max(maxx,num);
}
}
cout<<minn<<' '<<maxx<<endl;
return 0;
}
C
一个显然的想法是对于一个很大块的图形,应当按照有内到外进行覆盖。
反向地考虑,则与空白直接相连的区域 \(S_1\) 可以最后覆盖,不与空白相连但与 \(S_1\) 直接相连的区域可以在倒数第二步覆盖……
于是枚举所有空白位置开始进行 BFS 即可求得图形的最内层至少是倒数第几步覆盖的——即最少操作次数。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 1010;
const int ex[4] = {0, 0, 1, -1};
const int ey[4] = {1, -1, 0, 0};
//=============================================================
int n, m, val[kN][kN];
bool vis[kN][kN];
std::string map[kN];
//=============================================================
void BFS() {
std::queue <pii> q;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
val[i][j] = kN;
if (map[i][j] == '-') {
val[i][j] = 0;
q.push(mp(i, j));
}
}
}
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
if (map[i][j] == '-') continue;
if (i == 0 || j == 0 || i == n - 1 || j == m - 1) {
val[i][j] = 1;
q.push(mp(i, j));
}
}
}
while (!q.empty()) {
pii p = q.front(); q.pop();
int x = p.first, y = p.second;
if (vis[x][y]) continue;
vis[x][y] = 1;
for (int i = 0; i < 4; ++ i) {
int nx = x + ex[i], ny = y + ey[i];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
val[nx][ny] = std::min(val[nx][ny], val[x][y] + 1);
q.push(mp(nx, ny));
}
}
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m;
for (int i = 0; i < n; ++ i) std::cin >> map[i];
BFS();
int ans = 1;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
ans = std::max(ans, val[i][j]);
}
}
std::cout << ans << "\n";
return 0;
}
I
爆搜题。
若给定的 \(n\) 个单词中出现超过 18 种字符则无解,否则补齐 18 种字符。
然后直接爆搜枚举三个字符集合并检查即可,集合总数为:
加上剪枝之后跑得飞快。
#include <bits/stdc++.h>
using namespace std;
int n, A[10], B[10], o;
char s[1010][5], S[30];
bool vis[300], vis2[300], p[300];
void dfs2(int k) {
if (k == 7) {
for (int i = 1; i <= n; i++) {
int cnt = (int)vis2[s[i][0]] + (int)vis2[s[i][1]] + (int)vis2[s[i][2]];
if (cnt != 1) return ;
}
for (int i = 1; i <= 6; i++) cout << S[A[i]]; cout << " ";
for (int i = 1; i <= 6; i++) cout << S[B[i]]; cout << " ";
for (int i = 1; i <= o; i++) if (!vis[S[i]] && !vis2[S[i]]) cout << S[i]; cout << " ";
exit(0);
}
for (int i = B[k - 1] + 1; i <= o; i++)
if (!vis[S[i]]) {
B[k] = i;
vis2[S[i]] = true;
dfs2(k + 1);
vis2[S[i]] = false;
}
}
void dfs(int k) {
if (k == 7) {
for (int i = 1; i <= n; i++) {
int cnt = (int)vis[s[i][0]] + (int)vis[s[i][1]] + (int)vis[s[i][2]];
if (cnt != 1) return ;
}
dfs2(1);
return ;
}
for (int i = A[k - 1] + 1; i <= o; i++)
{
A[k] = i;
vis[S[i]] = true;
dfs(k + 1);
vis[S[i]] = false;
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i];
if (s[i][0] == s[i][1] || s[i][0] == s[i][2] || s[i][1] == s[i][2]) {
cout << "0\n";
return 0;
}
p[s[i][0]] = p[s[i][1]] = p[s[i][2]] = true;
}
o = 0;
for (char c = 'a'; c <= 'z'; c++) {
if (p[c]) S[++o] = c;
}
if (o > 18) {
cout << "0\n";
return 0;
}
for (char c = 'a'; c <= 'z'; c++) {
if (!p[c] && o < 18) S[++o] = c;
}
dfs(1);
cout << "0\n";
return 0;
}
K
妈的这次题面怎么都这么难读、、、
因为人是按照 DFS 序寻路,相当于走到最左侧的叶子,则删点也一定是按照 DFS 序删的,这样每删去一个点影响最小,相当于只剪去了最左侧的叶子。
当出现第一个无法到达目标的人时即结束,于是考虑进行 DFS 枚举节点并在此过程中维护删到该节点时可以处理到第几个人的目标,仅需检查询问目标位置是否在根到该节点的链上即可,当出现某人的目标已被删掉时说明结束。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int n,m,tot,head[N];
int d[N],now;
struct node{
int to,nxt;
}e[N<<1];
void add(int u,int v){
e[++tot].to=v,e[tot].nxt=head[u],head[u]=tot;
}
bool ok[N];
void dfs(int u){
ok[u]=1;
// cout<<u<<endl;
// for(int i=1;i<=n;++i){
// cout<<ok[i]<<' ';
// } cout<<endl;
while(now<m&&ok[d[now+1]]){
++now;
}
vector<int>so;
for(int i=head[u];i;i=e[i].nxt){
so.push_back(e[i].to);
}
sort(so.begin(),so.end());
for(auto v:so){
dfs(v);
}
ok[u]=0;
}
signed main(){
cin>>n>>m;
for(int i=1,u,v;i<n;++i){
cin>>u>>v;
add(u,v);
}
now=0;
for(int i=1;i<=m;++i){
cin>>d[i];
}
dfs(1);
cout<<now<<endl;
return 0;
}
L
DP。
场上大神 dztle 用大力区间 DP 草过去了,然而 std 只是线性 DP。
懒得补了,略。
Code by dztle:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=105;
char s[N];
int a[N],sum;
int ans=N*N;
int f[N][N][2];
void doit(){
memset(f,0x3f,sizeof(f));
for(int i=1;i<=sum;++i){
if(a[i]==0) f[i][i][0]=0;
else f[i][i][1]=0;
}
for(int i=1;i<sum;++i){
if(a[i]==0&&a[i+1]==0) f[i][i+1][0]=0,f[i][i+1][1]=1;
if(a[i]==0&&a[i+1]==1) f[i][i+1][0]=1,f[i][i+1][1]=2;
if(a[i]==1&&a[i+1]==0) f[i][i+1][0]=1,f[i][i+1][1]=2;
if(a[i]==1&&a[i+1]==1) f[i][i+1][0]=1,f[i][i+1][1]=0;
}
for(int L=3;L<=sum;++L){
for(int i=1;i<=sum;++i){
int r=i+L-1;
if(r>sum) break;
for(int mid=i;mid<r;++mid){
for(int ty=0;ty<=1;++ty){
f[i][r][ty]=min(f[i][r][ty],f[i][mid][ty]+f[mid+1][r][ty]);
}
}
if(L%2==0){
f[i][r][0]=min(f[i][r][0],f[i][r][1]+(L/2));
f[i][r][1]=min(f[i][r][1],f[i][r][0]+(L/2));
}else{
f[i][r][0]=min(f[i][r][0],f[i][r][1]+(L/2)+1);
f[i][r][1]=min(f[i][r][1],f[i][r][0]+(L/2)+2);
}
if((r-i)%2==0) f[i][r][1]=min(f[i+2][r][0]+1+(r-i)/2,f[i][r][1]);
else f[i][r][1]=min(f[i+2][r][0]+1+(r-i)/2+2,f[i][r][1]);
if((r-i)%2==0) f[i][r][1]=min(f[i][r-2][0]+1+(r-i)/2,f[i][r][1]);
else f[i][r][1]=min(f[i][r-2][0]+1+(r-i)/2+2,f[i][r][1]);
}
}
ans=min(ans,f[1][sum][1]);
}
signed main(){
scanf("%s",s+1);
int len=strlen(s+1);
if(len%2==0){
sum=len/2;
for(int i=1;i<=sum;++i){
if(s[i]==s[len-i+1]) a[i]=1;
else a[i]=0;
}
++sum;
a[sum]=1;
doit();
a[sum]=0;
doit();
}else{
sum=len/2; ++sum;
for(int i=1;i<sum;++i){
if(s[i]==s[len-i+1]) a[i]=1;
else a[i]=0;
}
a[sum]=1;
doit();
a[sum]=0;
doit();
}
cout<<ans;
return 0;
}
/*
abbaaaabb
*/
F
DP。
发现每次检查之后相当于抛弃了之前所有天数的贡献,考虑 DP。
记 \(f_{i, j}\) 表示在第 \(i\) 天时一共打扫了 \(j\) 次最少可以使得房间内剩余多少垃圾,转移时枚举当前天数 \(i\) 与操作次数 \(j\),讨论转移即可,则有:
特别地,当第 \(i\) 天需要被检查时,需要检查是否有 \(\forall 0\le j\le i, f_{i, j} = 0\),若没有则无解,否则需要将所有 \(f_{i, j}\not= 0\) 设为 \(+\infin\) 表示该状态之后无贡献。
答案即为最后一次检查时最小的满足 \(f_{i, j}=0\) 的 \(j\) 。
总时间复杂度 \(O(n^2)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1010;
const LL kInf = 1e18 + 2077;
//=============================================================
int n, d, m[kN], c[kN];
bool v[kN];
LL f[kN][kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> d;
for (int i = 1; i <= n; ++ i) {
std::cin >> m[i] >> c[i];
}
for (int i = 1; i <= d; ++ i) {
int x; std::cin >> x;
v[x] = 1;
}
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= n; ++ j) {
f[i][j] = kInf;
}
}
int flag = 0, ans = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= i; ++ j) {
if (j > 0)
f[i][j] = std::min(f[i][j], std::max(f[i - 1][j - 1] + m[i] - c[i], 0ll));
f[i][j] = std::min(f[i][j], f[i - 1][j] + m[i]);
}
if (v[i]) {
ans = n + 1;
for (int j = 0; j <= i; ++ j) {
if (f[i][j] == 0) {
ans = j;
break;
}
}
if (ans == n + 1) flag = 1;
for (int j = 0; j <= i; ++ j) {
if (j < ans) f[i][j] = kInf;
}
}
}
if (flag) std::cout << -1 << "\n";
else std::cout << ans << "\n";
return 0;
}
/*
1 1
100 1
1
*/
G
二维偏序。
妈的题面怎么这么难读
记点:\(O(0, 0)\),\(X(x, 0)\),\(A(x_i, y_i)\),\(B(x_j, y_j)\)。
发现对于点 \(A\),点 \(B\) 被包含在三角形 \(\triangle AOX\) 等价于 \(\triangle BOX\) 被包含在 \(\triangle AOX\) 中。则充要条件为:
发现是一个二维偏序问题,于是考虑求得上述两个角度的偏序关系后树状数组求解即可。
总时间复杂度 \(O(n\log n)\) 级别。
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e5 + 10;
int n, x; LL ans[N];
struct node {
int x, y, v, id, d;
} p[N];
LL sum[N];
void add(int k, int x) {
for ( ; k <= n; k += (k & -k)) sum[k] += x;
}
LL qry(int k) {
LL res = 0;
for ( ; k; k ^= (k & -k)) res += sum[k];
return res;
}
inline bool cmp2(const node &g, const node &h) {
return 1ll * (h.x - x) * g.y > 1ll * (g.x - x) * h.y;
}
inline bool cmp1(const node &g, const node &h) {
return 1ll * h.x * g.y < 1ll * g.x * h.y;
}
int main() {
cin >> n >> x;
for (int i = 1; i <= n; i++) {
cin >> p[i].x >> p[i].y >> p[i].v;
p[i].id = i;
}
sort(p + 1, p + n + 1, cmp2);
for (int i = 1; i <= n; i++) p[i].d = i;
sort(p + 1, p + n + 1, cmp1);
for (int i = 1; i <= n; i++) {
ans[p[i].id] = qry(p[i].d);
add(p[i].d, p[i].v);
}
for (int i = 1; i <= n; i++) cout << ans[i] << "\n";
return 0;
}
D
构造。
Code by Saya_Alter_:
#include <bits/stdc++.h>
#define mod 998244353
using namespace std;
using LL = long long;
// #define int long long
const int N = 5e5 + 10;
LL K, f[500][12], nex[12], g[12];
string s, t = "ATELLITE", ans, st = "SATELLITE";
void solve() {
cin >> K;
int i;
for (i = 1; f[i - 1][8] <= K; i++) {
s += t;
memset(f[i], 0, sizeof(f[i]));
f[i][0] = 1;
for (int j = 0; j < s.size(); j++) {
nex[0] = 1;
for (int k = 0; k < 8; k++) {
nex[k + 1] = f[i][k + 1];
if (s[j] == t[k]) nex[k + 1] += f[i][k];
}
swap(f[i], nex);
}
// cout << f[i][8] << " ";
}
// cout << "\n";
// cout << i << " ";
for (int j = i - 2; j >= 1; j--) {
for (int o = 1; o <= K / f[j][8]; o++) ans += 'S';
K %= f[j][8];
ans += t;
}
g[0] = 1;
for (int j = 0; j < ans.size(); j++) {
nex[0] = 1;
for (int k = 0; k < 9; k++) {
nex[k + 1] = g[k + 1];
if (ans[j] == st[k]) nex[k + 1] += g[k];
}
swap(g, nex);
}
// cout << g[9] << "\n";
// cout << ans.size() << "\n";
cout << ans;
// cout << "\n";
}
int main() {
// ios::sync_with_stdio(false),cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}
//1000000000000000000
写在最后
我是飞舞。
学到了什么:
- I:发现可以缩小需要枚举的范围到很小的话直接大力搜!
- G:转化为偏序问题。