YbtOJ 「数据结构」 第6章 倍增问题
A. 【例题1】查找编号
[题目描述]
输入\(n\)个不超过\(10^9\)的单调不减的(就是后面的数字不小于前面的数字)非负整数\(a_1,a_2\dots a_n\),第\(i\)个数的编号为\(i\),然后进行\(m\)次询问。对于每次询问,给出一个整数\(q\),要求输出这个数字在序列中的编号,如果没有找到的话输出 -1。当多个数值相同时,这个数的编号取最靠前的那个。
[输入格式]
第一行\(2\)个整数\(n\)和\(m\),表示数字个数和询问次数。
第二行\(n\)个整数,表示这些待查询的数字。
第三行\(m\)个整数,表示询问这些数字的编号,从 开始编号。
[输出格式]
一行\(m\)个整数表示每个询问对应的答案。
[算法分析]
倍增板子 确定第一个小于目标值的位置
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e6 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int m , n , a[N] , x;
int get ( int x )
{
int ans = 0;
for ( int i = 20 ; i >= 0 ; i -- )
if ( ans + (1<<i) <= n )
if ( a[ans+(1<<i)] < x )//右面指针小于目标值
ans += (1<<i);
if ( a[ans+1] == x ) return ans + 1;
else return -1;
}
int main()
{
scanf ( "%d%d" , &n , &m );
for ( int i = 1 ; i <= n ; i ++ ) scanf ( "%d" , &a[i] );
while ( m -- ) printf ( "%d " , get(read()));
return 0;
}
C. 1.跑路上班
[题目描述]
小 A 的工作不仅繁琐,更有苛刻的规定,要求小 A 每天早上在 \(6:00\) 之前到达公司,否则这个月工资清零。可是小 A 偏偏又有赖床的坏毛病。于是为了保住自己的工资,小 A 买了一个空间跑路器,每秒钟可以跑 \(2^k\) 千米(\(k\) 是任意自然数)。当然,这个机器是用 longint
存的,所以总跑路长度不能超过 maxlongint
千米。小 A 的家到公司的路可以看做一个有向图,小 A 家为点 \(1\),公司为点 \(n\),每条边长度均为一千米。小 A 想每天能醒地尽量晚,所以让你帮他算算,他最少需要几秒才能到公司。数据保证 \(1\) 到 \(n\) 至少有一条路径。
[输入格式]
第一行两个整数 \(n,m\),表示点的个数和边的个数。
接下来 \(m\) 行每行两个数字 \(u,v\),表示一条 \(u\) 到 \(v\) 的边。
[输出格式]
一行一个数字,表示到公司的最少秒数。
[算法分析]
处理所有可以一步到达的点 边权设置为1
最后跑一边floyd处理即可
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e2 + 5;
const int inf = 0x3f3f3f3f;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int m , n , a[N] , k , dis[N][N][N] , f[N][N];
int head[N] , cnt;
struct node { int to , nxt , w; } e[N];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w }; head[u] = cnt; }
signed main()
{
n = read() , m = read();
memset ( f , inf , sizeof f );
for ( int i = 1 , u , v ; i <= m ; i ++ )
u = read() , v = read() , dis[u][v][0] = f[u][v] = 1;
for ( int i = 1 ; i <= 64 ; i ++ )
for ( int k = 1 ; k <= n ; k ++ )
for ( int u = 1 ; u <= n ; u ++ )
for ( int v = 1 ; v <= n ; v ++ )
dis[u][v][i] = max ( dis[u][v][i] , dis[u][k][i-1] & dis[k][v][i-1] );
for ( int u = 1 ; u <= n ; u ++ )
for ( int v = 1 ; v <= n ; v ++ )
for ( int k = 1 ; k <= 64 ; k ++ )
if ( dis[u][v][k] ) { f[u][v] = 1; break; }
for ( int k = 1 ; k <= n ; k ++ )
for ( int u = 1 ; u <= n ; u ++ )
for ( int v = 1 ; v <= n ; v ++ )
f[u][v] = min ( f[u][v] , f[u][k] + f[k][v] );
printf ( "%lld" , f[1][n] );
return 0;
}
D. 2.图上查询
[题目描述]
有一个\(n\)个点\(n\)条边的有向图,第\(i\)条边从\(i-1\)指向\(f_i\),边权为\(w_i\)的边,对每个点求从它出发经过\(k\)条边,这\(k\)条边的权值和 \(s_i\)以及这\(k\)条边的权值最小值\(m_i\)
[输入格式]
第一行两个数\(n,k\)
第二行\(n\)个数\(f_i\)
第三行\(n\)个数\(w_i\)
[输出格式]
每行两个数\(s_i\),\(m_i\).
[代码实现]
节点从0开始标号
\(nxt[i][k]\)表示节点\(i\)跳\(2^j\)步到达的节点,\(mn[i][j]\)表示节点\(i\)跳\(2^j\)步之间路长度的最小值,\(sum[i][j]\)表示的路径长度和。
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int m , n , fa[N][40] , minn[N][40] , sum[N][40];
signed main()
{
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) fa[i][0] = read() + 1;
for ( int i = 1 ; i <= n ; i ++ ) sum[i][0] = minn[i][0] = read();
for ( int j = 1 ; j <= 35 ; j ++ )
for ( int i = 1 ; i <= n ; i ++ )
fa[i][j] = fa[fa[i][j-1]][j-1];
for ( int j = 1 ; j <= 35 ; j ++ )
for ( int i = 1 ; i <= n ; i ++ )
minn[i][j] = min ( minn[i][j-1] , minn[fa[i][j-1]][j-1] );
for ( int j = 1 ; j <= 35 ; j ++ )
for ( int i = 1 ; i <= n ; i ++ )
sum[i][j] = sum[i][j-1] + sum[fa[i][j-1]][j-1];
for ( int i = 1 ; i <= n ; i ++ )
{
int mn = inf , summ = 0 , now = i;
for ( int j = 35 ; j >= 0 ; j -- )
{
if ( m & ( 1ll << j ) )
{
summ += sum[now][j];
mn = min ( mn , minn[now][j] );
now = fa[now][j];
}
}
printf ( "%lld %lld\n" , summ , mn );
}
return 0;
}