【题解】10.6 国庆中秋 提高组 热身赛
1.栅栏密码
题目传送门
题目大意:
给定高度 h 和一行使用栅栏密码加密的密文字符串 s,请你输出一行明文字符串 plain。
即把明文排列成若干个\/\/\/的形状,然后再逐行按从左到右的顺序取出字符,形成密文。
STEP 1.
直观一点,用数字来表示一下:
1###5###9
#2#4#6#8#10
##3###7###11
即可排列出 1 5 9 2 4 6 8 10 3 7 11.
进而发现:
\(1^{+4\curvearrowright}5^{+4\curvearrowright}9\)
\(2^{+2\curvearrowright}4^{+2\curvearrowright}6^{+2\curvearrowright}8^{+2\curvearrowright}10\)
\(3^{+4\curvearrowright}7^{+4\curvearrowright}11\)
再看一组:
1#####7#####13
#2###6#8###12
##3#5###9#11
###4#####10
即可排列出 1 7 13 2 6 8 12 3 5 9 11 4 10.
进而发现:
\(1^{+6\curvearrowright}7^{+6\curvearrowright}13\)
\(2^{+4\curvearrowright}6^{+2\curvearrowright}8^{+4\curvearrowright}12\)
\(3^{+2\curvearrowright}5^{+3\curvearrowright}9^{+2\curvearrowright}11\)
\(4^{+6\curvearrowright}10\)
STEP 2.
可以发现,当 \(h\) 为 \(3\) 时,一共分成了 \(3\) 行,打乱前的\(1\)、\(5\)、\(9\)分别变成了打乱后的第\(1\)、\(2\)、\(3\)个数,相邻两个数之间相差 \(2\space·(h−1)\),第 \(h\) 行同理。
观察第 \(2\) 行发现,首行和尾行的间隔不变,若行数为 \(i\),当前数为第 \(2\) 行的第奇数个数的时候,下一个数字下标就多 \(2\space·(h−i)\),若当前数为第 \(2\) 行的第偶数个数的时候,下一个数字下标就多 \(2\space·(i−1)\)。而且每一行的第一个数字就是这一行的行数。
具体来说,
\( next(x)=\begin{cases} x+2\space·(h-i)\qquad i\%2=1\\ x+2\space·(i-1)\qquad i\%2=0 \end{cases} \)
STEP 3.
根据此思路,我们可以写出代码:
代码(凭良心复制)
#include<bits/stdc++.h>
using namespace std;
string s;
char ans[100080];
int hang=0;
int h,n,x,y;
int main(){
cin>>h>>s;
n=s.size();
s=" "+s;
hang=x=y=1;
for(int i=1;i<=n;i++){
ans[x]=s[i];
if(hang==1||hang==h)x+=(h-1)*2;//在两端
else{
if(y%2==1)x+=(h-hang)*2; //在奇数列
else x+=2*(hang-1); //在偶数列
}
y++;
if(x>n){//超出了
x=++hang;//换行
y=1;
}
}
for(int i=1;i<=n;i++)cout<<ans[i];
return 0;
}
2.旅行计划
题目传送门
题目大意:
给定一个有向图,求得以第 i 个节点为终点的最长路径
STEP 1.
看到以第 i 个节点为终点就可以想到需要反向存边,这样终点就变为起点了.
由于只能向一个方向走,所以就为有向图。
STEP 2.
挺简单的,写个 DFS 就可以了
根据此思路,我们可以写出代码:
代码(凭良心复制)
#include<bits/stdc++.h>
using namespace std;
int dis[100005],n,m,x,y;
vector<int> mp[100005];
int dfs(int n){
if(dis[n]!=0)return dis[n];
dis[n]=1;
for(auto it:mp[n]){
dis[n]=max(dis[n],dfs(it)+1);
}
return dis[n];
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>x>>y;
mp[y].push_back(x);//反着存
}
for(int i=1;i<=n;i++){
dfs(i);
}
for(int i=1;i<=n;i++){
cout<<dis[i]<<'\n';
}
return 0;
}
3.商务旅行
题目传送门
题目大意:
给定一棵树,再给一个序列S1、S2、s3…… Sn,求 \(\sum\limits_{1≤i<n}^i len(S_i+S_{i+1})\)
STEP 1.
最短路问题,由于树上两点之间只有一条路径,我们可以想到这幅图:

我们可以发现,点 3(蓝色)和点 7(红色)的最短路径(绿色),其实就是求他们的线段数量。
我们为每一条线段赋值均为 1 ,但是不想用链式前向星怎么办,我们可以存点权。
就变成了树上前缀和
STEP 2.
我们将树的深度与前缀和数组列出来,发现它们简直一模一样,太好了,不用单独维护前缀和数组了 于是我们就可以用树的深度来替代前缀和数组

代码(凭良心复制)
#include<bits/stdc++.h>
using namespace std;
int n,a,b,m,ans;
int dep[30005],fa[30005][20],x,y=1,z;
vector<int> tr[30005];
int qzh[30005];
void dfs(int n,int f,int d){
dep[n]=d;
qzh[n]=qzh[f]+1;
fa[n][0]=f;
for(int i=1;i<=15;i++){
if(fa[n][i-1] != 0)
fa[n][i]=fa[fa[n][i-1]][i-1];
else
fa[n][i]=0;
}
for(auto it:tr[n]){
if(it == f) continue; // 避免重复访问父节点
dfs(it,n,d+1);
}
}
int lca(int a,int b){
if(dep[a]<dep[b])swap(a,b);
for(int i=15;i>=0;i--){// 将a跳到与b同一深度
if(fa[a][i] != 0 && dep[fa[a][i]] >= dep[b])
a=fa[a][i];
}
if(a == b) return a;
for(int i=15;i>=0;i--){// 同时向上跳
if(fa[a][i] != 0 && fa[a][i] != fa[b][i]){
a=fa[a][i];
b=fa[b][i];
}
}
return fa[a][0];
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
cin>>a>>b;
tr[a].push_back(b);
tr[b].push_back(a);
}
dfs(1,0,0);
cin>>m;
for(int i=1;i<=m;i++){
cin>>z;
x=y,y=z;
ans+=qzh[x]+qzh[y]-2*qzh[lca(x,y)];
}
cout<<ans;
return 0;
}
4.鬼子进村
题目传送门
题目大意:
给定一条有 n 个节点且节点全部为 0 的一条链,再给出 m 个 3 种类型的操作,分别为
D x:将第 x 个节点标记为 1
R :将上一个被标记为 1 的节点标记为 0
Q x: 求 x 处在的位置的 0 的最大长度
STEP 1.
依照题意,我们可以立即想到使用栈来维护命令 D和命令 R。对于命令 Q,我们就遍历查找左界右界:
#include<bits/stdc++.h>
using namespace std;
int n,m,x;
char op;
int d[50005],top=0;//摧毁顺序
bool b[50005];//是否摧毁
int find(int a){
int ans=1;
if(b[a])return 0;
for(int i=a-1;i>=1&&b[i]==0;i--){
ans++;
}
for(int i=a+1;i<=n&&b[i]==0;i++){
ans++;
}
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>op;
if(op=='D'){
cin>>x;
d[++top]=x;
b[x]=1;
}
if(op=='R'){
b[d[top]]=0;
top=max(top-1,0);
}
if(op=='Q'){
cin>>x;
cout<<find(x)<<'\n';
}
}
return 0;
}
\(81\space pts.\)
STEP 2.
由于链表极长的长度,导致了超时。
换个思路,若直接遍历被摧毁的房屋,即可得到区间的左右界,相减再减一即可
唯一的细节就是要特判是否直接被摧毁:
若士兵被围堵在摧毁了的房子中,那只能等死了。。。。。。
int find(int a) {
if (b[a]) return 0;
int l = 0, r = n + 1;
for (int i = a - 1; i >= 1; i--) {
if (b[i]) {
l = i;
break;
}
}
for (int i = a + 1; i <= n; i++) {
if (b[i]) {
r = i;
break;
}
}
return r - l - 1;
}
代码(凭良心复制)
#include<bits/stdc++.h>
using namespace std;
int n, m, x;
char op;
int d[50005], top = 0; //摧毁顺序
bool b[50005];//是否摧毁
int find(int a) {
if (b[a]) return 0;
int l = 0, r = n + 1;
for (int i = a - 1; i >= 1; i--) {
if (b[i]) {
l = i;
break;
}
}
for (int i = a + 1; i <= n; i++) {
if (b[i]) {
r = i;
break;
}
}
return r - l - 1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> op;
if (op == 'D') {
cin >> x;
d[++top] = x;
b[x] = 1;
}
if (op == 'R') {
b[d[top]] = 0;
top = max(top - 1, 0);
}
if (op == 'Q') {
cin >> x;
cout << find(x) << '\n';
}
}
return 0;
}

浙公网安备 33010602011771号