【题解】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.

最短路问题,由于树上两点之间只有一条路径,我们可以想到这幅图:

image

我们可以发现,点 3(蓝色)和点 7(红色)的最短路径(绿色),其实就是求他们的线段数量。

我们为每一条线段赋值均为 1 ,但是不想用链式前向星怎么办,我们可以存点权。

就变成了树上前缀和

STEP 2.

我们将树的深度与前缀和数组列出来,发现它们简直一模一样,太好了,不用单独维护前缀和数组了 于是我们就可以用树的深度来替代前缀和数组

image

代码(凭良心复制)
#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;
}
posted @ 2025-10-07 20:03  yangmengcheng1103  阅读(28)  评论(0)    收藏  举报