SZTU-ACM 2021 春期训练赛第一点五场 [组队赛](第四届河南大学生程序设计竞赛)总结和题解
A 序号互换
题目意思很简单,就是关于26进制的互相转换,如果是26进制换10进制,直接把每一位按顺序乘26累计到下一位即可获得最终结果。如果是10进制转26进制,我们首先要把字符串转化为整形。然后每一位对26取模。逐渐除26就能得到正确的26进制。注意临时oj不能识别万能头文件,会爆ce。但是标称给的还是万能头,大家如果要补题自己改一下就好了。
#include<bits/stdc++.h>
using namespace std;
char c[100001], b[30];
int main()
{
int t;
long long int ans;
string s, ans1;
b[0] = 'Z', b[1] = 'A';
for(int i = 2; i <= 26; i++)
b[i] = b[i - 1] + 1;
cin >> t;
while(t--)
{
cin >> s;
if(s[0] >= '0' && s[0] <= '9')
{
int l = s.size();
ans = 0;
for(int i = 0; i < l; i++)
ans = ans * 10 + s[i] - '0';
int k = 0;
while(ans)
{
c[k++] = b[ans % 26];
if(ans % 26 == 0)
ans = ans / 26 - 1;
else
ans /= 26;
}
for(int i = k - 1; i >= 0; i--)
printf("%c", c[i]);
printf("\n");
}
else
{
int q = 0;
int ll = s.size();
for(int i = 0; i < ll; i++)
q = q * 26 + (s[i] - 'A' + 1);
printf("%d\n", q);
}
}
return 0;
}
B 节能
这道题目很明显是一道区间dp的题目,但是很难直接设计出状态转移的方式。
dp [i] [j] [0]表示i到j的路灯都j已经关闭,机器人在路灯i的位置,此时已经消耗的最小电能
dp [i] [j] [1]表示i到j的路灯都已经关闭,机器人在路灯j的位置,此时已经消耗的最小电能
这样就可以得到状态转移方程为
dp [i] [j] [0] = min(dp[i+1] [j] [0]+[i+1,j]路段以外未关闭路灯在机器人从i+1走的i期间消耗的电能,dp [i+1] [j] [1]+[i+1,j]路段以外未关闭路灯在机器人从j走到i期间消耗的电能)
dp [i] [j] [1] = min(dp [i] [j-1] [0]+[i,j-1]路段以外未关闭路灯在机器人从i走到j期间消耗的电能,dp [i] [j-1] [1]+[i,j-1]路段以外未关闭路灯在机器人从j-1走到j期间消耗的电能)
对于某一段在某段时间里消耗的电能,我们可以通过维护一个前缀和来快速完成。推完状态转移方程,题目就很好写了,照着dp写就好了,需要注意的是,每一段的dp值需要初始化,而且要注意初始化0和1的顺序。错了就不能达到区间转移的效果,更多细节在代码里有注释,大家自行阅读理解
#include<bits/stdc++.h>
using namespace std;
int f[1005][1005][2], d[1005], w[1005];
//f[i][j][0]:i~j之间的灯全部关闭,且机器人此时站在i处
//f[i][j][1]:i~j之间的灯全部关闭,且机器人此时站在j处。
int main()
{
int n, k;
while(~scanf("%d", &n))
{
int sum = 0;
scanf("%d", &k);
for(int i = 1; i <= n; i++) //对路灯功率维护前缀和 ,距离本身就是前缀和不需要自己维护
{
scanf("%d%d", &d[i], &w[i]);
sum += w[i];
w[i] += w[i - 1];
}
f[k][k][0] = f[k][k][1] = 0;
for(int i = k + 1; i <= n; i++) //初始化向右走的区间
{
f[k][i][1] = f[k][i - 1][1] + (d[i] - d[i - 1]) * (sum - w[i - 1] + w[k - 1]);
//从k出发最终停留到i点,是由从k出发停留在i-1点转移得到的,需要减去k到i-1部分的电费 ;
f[k][i][0] = f[k][i][1] + (d[i] - d[k]) * (sum - w[i] + w[k - 1]);
//从i回到k需要的电费;
}
for(int i = k - 1; i > 0; i--)
{
f[i][k][0] = f[i + 1][k][0] + (d[i + 1] - d[i]) * (sum - w[k] + w[i]);
f[i][k][1] = f[i][k][0] + (d[k] - d[i]) * (sum - w[k] + w[i - 1]);
}
for(int i = k - 1; i > 0; i--)
for(int j = k + 1; j <= n; j++)
{
f[i][j][0] = min(f[i + 1][j][0] + (d[i + 1] - d[i]) * (sum - w[j] + w[i]),
f[i + 1][j][1] + (d[j] - d[i]) * (sum - w[j] + w[i]));
f[i][j][1] = min(f[i][j - 1][1] + (d[j] - d[j - 1]) * (sum - w[j - 1] + w[i - 1]),
f[i][j - 1][0] + (d[j] - d[i]) * (sum - w[j - 1] + w[i - 1]));
}
printf("%d\n", min(f[1][n][0], f[1][n][1]));
}
return 0;
}
C 表达式求值
这道题目应该是很经典的一道关于栈的应用,我们用一个栈存储数字,一个栈存储符号,当遇到)的时候处理一次符号运算。符号数量一定只比栈少1个,最后数字栈中剩余的数字就是我们要求的数字,注意一些处理字符串的细节就好。
#include <bits/stdc++.h>
using namespace std;
char a[1000];
stack<int> q,f;
int main()
{
int n,c,l;
scanf("%d", &n);
while(n--)
{
scanf("%s", a);
int l=strlen(a);
for(int i=0;i<l;i++)
{
if(a[i]>='0'&&a[i]<='9')
{
int z=0;
while(a[i]>='0'&&a[i]<='9')
z = z*10+a[i]-'0',i++;
i--,q.push(z);
}
if(a[i]=='a'&&a[i+1]=='d')f.push(1);
if(a[i]=='m'&&a[i+1]=='i')f.push(2);
if(a[i]=='m'&&a[i+1]=='a')f.push(3);
if(a[i]==')')
{
int c;
int op=f.top();f.pop();
int a=q.top();q.pop();
int b=q.top();q.pop();
if(op==1)c=a+b;
if(op==2)c=min(a,b);
if(op==3)c=max(a,b);
q.push(c);
}
}
printf("%d\n", q.top());
}
return 0;
}
D 走迷宫
题目数据范围很小,很显然题目可以用搜索解决,我们只要处理如何搜出最大差值即可,不难发现,我们可以用二分来压缩差值的空间。每一次二分答案都dfs搜一遍能否走出迷宫,需要灵活把dfs和二分联系起来。
#include <bits/stdc++.h>
const int INF = 0x3fffffff;
int n, maxn, minn, f = 0;
int ret[150][150];
int vis[150][150];
int to_x[4] = {1, -1, 0, 0};
int to_y[4] = {0, 0, 1, -1};
inline int read_int()
{
char c;
int sign = 1;
while((c = getchar()) < '0' || c > '9')
if(c == '-') sign = -1;
int res = c - '0';
while((c = getchar()) >= '0' && c <= '9')
res = res * 10 + c - '0';
return res * sign;
}
void dfs(int x, int y, int L, int r)
{
if(f) return;
if(ret[x][y] > r || ret[x][y] < L) return;
if(x == n && y == n) { f = 1; return; }
for(int i = 0; i < 4; i++)
{
int xx = x + to_x[i];
int yy = y + to_y[i];
if(!vis[xx][yy] && xx >= 1 && xx <= n && yy >= 1 && yy <= n)
{
vis[xx][yy] = 1;
dfs(xx, yy, L, r);
}
}
}
bool judg(int x)
{
for(int i = minn; i <= maxn - x; i++)
{
f = 0;
memset(vis, 0, sizeof(vis));
int L = i, r = i + x;
vis[1][1] = 1;
dfs(1, 1, L, r);
if(f) return true;
}
return false;
}
int main()
{
while(scanf("%d", &n) != EOF)
{
maxn = 0 - INF;
minn = INF;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
ret[i][j] = read_int();
if(maxn < ret[i][j]) maxn = ret[i][j];
if(minn > ret[i][j]) minn = ret[i][j];
}
int L = 0, r = maxn - minn;
while(L < r)
{
int mid = (L + r) / 2;
if(judg(mid)) r = mid;
else L = mid + 1;
}
printf("%d\n", r);
}
return 0;
}
E 宝物
这道题目是最短路的一个变形,我们从入口开始,对每个点进行用最短路算法进行判断,只是把最短路的条件改为能出来时能携带的最大物品量。本质时保证能联通的最大边权最小的最短路,然后对宝藏进行排序,按顺序取出,如果不能取,就停下来,由于这道题的边数很少,属于稀疏图,我们直接写SPFA就行了,需要注意所有边都是双向边,数组要开3w。dijkstra也可以解决这道题目,大家有兴趣可以自己谢谢看
#include <bits/stdc++.h>
using namespace std;
struct node
{
int v, z, next;
} e[30020];
int head[8010], vis[8010], max1[8010], b[8010], dis[8010], cnt;
int addedge(int a, int b, int v)
{
e[++cnt].v = b;
e[cnt].z = v;
e[cnt].next = head[a];
head[a] = cnt;
}
void spfa(int s)
{
queue<int>q;
q.push(s);
vis[s] = 1;
dis[s] = 0x3f3f3f3f;
while(!q.empty())
{
int d = q.front();
q.pop();
vis[d] = 0;
for(int i = head[d]; i; i = e[i].next)
{
int v = e[i].v;
if(dis[v] < min(dis[d], e[i].z))
{
dis[v] = min(dis[d], e[i].z);
if(vis[v] == 0)
{
q.push(v);
vis[v] = 1;
}
}
}
}
}
int main()
{
int n, m, k, w, a, b, c,ans;
while(~scanf("%d%d%d%d", &n, &m, &k, &w))
{
memset(vis, 0, sizeof(vis));
memset(dis, 0, sizeof(dis));
memset(head, 0, sizeof(head));
cnt = 0;
for(int i = 0; i < k; i++)
scanf("%d", &b[i]);
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c);
addedge(b, a, c);
}
spfa(1);
for(int i = 0; i < k; i++)
max1[i] = dis[b[i]];
ans = 0;
sort(max1, max1 + k);
for(int i = 0; i < k; i++)
if(max1[i] >= (ans+1)*w)
ans++;
printf("%d\n", ans);
}
return 0;
}
F Substring
题意很简单,类似于回文串,只不过这次反转后字串只要在整个字符串中有即可。题目范围很小,我们直接枚举所有字串即可解决问题
#include <bits/stdc++.h>
char s[105], a[105];
int n, maxn, b, e;
int main()
{
scanf("%d", &n);
while(n--)
{
maxn = 0;
scanf("%s", s);
int len = strlen(s);
for(int i = 0; i < len; i++)
a[len - i - 1] = s[i];
for(int i = 0; i < len - 1; i++)
for(int j = i + 1; j < len; j++)
{
for(int k = 0; k < len - j + i; k++)
{
int judg = 0;
for(int p = 0; p < j - i + 1; p++)
if(s[i + p] != a[k + p])
{
judg = 1;
break;
}
if(!judg && maxn < j - i + 1)
{
b = i;
e = j;
maxn = j - i + 1;
}
}
}
if(maxn)
for(int i = b; i <= e; i++) printf("%c", s[i]);
else printf("%c", s[0]);
printf("\n");
}
return 0;
}
G BOBSLEDDING
题目大意是我们需要保证在转弯时速度不能超过多少,每秒速度变化最多为1.问整个过程中所能保持的最大速度时多少。我们只需要先假设他一直加速,直到遇到转弯,更新当前最大速度和前面所有需要更新的值即可
#include<bits/stdc++.h>
using namespace std;
int f[100010];
int main()
{
int a,b,l,n;
while(~scanf("%d%d",&l,&n))
{
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
scanf("%d%d",&a,&b),f[a]=b;
f[0]=1;
for(int i=1;i<=l;i++)
{
if(f[i]==0||f[i]>f[i-1]+1)
f[i]=f[i-1]+1;
else{
int q=i;
while(f[i]+1<f[i-1])
f[i-1]=f[i]+1,i--;
}
}
int ans=0;
for(int i=0;i<=l;i++)
ans=min(ans,f[i]);
printf("%d\n",ans);
}
return 0;
}
H SECRET
题目给了一个加权无向图,从起点道终点共走T次,每条路只能走一次。问如何才能让权值最小的边只能走一次。对于这个题目,我们要求最小的最大权值。对于权值,显然不能枚举得出,所以我们二分权值,对于每一个二分的权值,选择满足条件的边标记为流量为1,标记起点终点用dinic跑一遍最大流。得到的最大流量就是能到达终点的次数。判断是否大于T即可结合二分答案确定最大权值
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int N, P, T;
struct Edge
{
int u, v, w;
} e[40010];
int g[220][220], vis[220], d[220];
bool bfs()
{
memset(vis, 0, sizeof(vis));
vis[1] = 1;
queue<int>q;
q.push(1);
d[1] = 0;
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = 1; i <= N; ++i)
{
if(!vis[i] && g[u][i] > 0)
{
vis[i] = 1;
d[i] = d[u] + 1;
q.push(i);
}
}
}
return vis[N];
}
int dfs(int u, int a)
{
if(u == N || a == 0) return a;
int flow = 0, f;
for(int i = 1; i <= N; ++i)
{
if(g[u][i] && d[i] == d[u] + 1 && (f = dfs(i, min(a, g[u][i]))) > 0)
{
g[u][i] -= f;
g[i][u] += f;
flow += f;
a -= f;
if(!a) break;
}
}
return flow;
}
int solve()
{
int res = 0;
while(bfs())
{
res += dfs(1, inf);
}
return res;
}
bool ok(int mid)
{
memset(g, 0, sizeof(g));
for(int i = 1; i <= P; ++i)
{
if(e[i].w <= mid)
{
g[e[i].u][e[i].v]++;
g[e[i].v][e[i].u]++;
}
}
return solve() >= T;
}
int main()
{
while(scanf("%d%d%d", &N, &P, &T) != EOF)
{
for(int i = 1; i <= P; ++i) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
int l = 0, r = 1000010;
while(l < r)
{
int mid = l + ((r - l) >> 1);
if(ok(mid)) r = mid;
else l = mid + 1;
}
cout << l << endl;
}
return 0;
}

浙公网安备 33010602011771号