递归/分治

递归/分治

P1010 [NOIP1998 普及组] 幂次方

被普及-创飞力()

我们显然是先对于该数的最大的 \(2\) 的次幂进行分解 然后对于指数再进行分解 如果有剩余 那么用 \(+\) 号连接

写一个递归即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 1e6 + 5;
const int mod = 1e9 + 7;

int read()
{
	int f = 1 , x = 0;
	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 n;

void solve ( int x )
{
	if ( x == 1 ) { cout << "2(0)"; return; }
	if ( x == 2 ) { cout << "2"; return; }
	int cnt = 0;
	while ( ( 1 << cnt ) <= x ) ++ cnt; -- cnt;
	if ( cnt == 1 ) cout << 2;
	else cout << "2(" , solve(cnt) , cout << ")";
	if ( x - ( 1 << cnt ) > 0 ) cout << "+" , solve ( x - ( 1 << cnt ) );
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	solve(n);
	return 0;
}

P1885 Moo

显然是一个分治

我们可以先求出来 \(n\) 位是在第几个字符串中第一次出现

那么我们可以递归解决这个问题 可以将第 \(k\) 个字符串分成三个部分:前面的 \(k-1\) 串 中间的串 后面的 \(k-1\)

这三个串我们分别判断 如果是第一或者第三部分我们递归下去判断即可 否则中间的直接返回对应的 \(o\) 或者 \(m\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
#define int long long
const int N = 1e6 + 5;

int read()
{
	int f = 1 , x = 0;
	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 n , pos , len[N];

char solve ( int x , int l ) // l层的x位置
{
	if ( l == 0 ) return x == 1 ? 'm' : 'o';
	if ( x <= len[l-1] ) return solve ( x , l - 1 );
	else if ( x == len[l-1] + 1 ) return 'm';
	else if ( x > len[l] - len[l-1] ) return solve ( x - ( len[l] - len[l-1] ) , l - 1 );
	else return 'o';
}

signed main ()                                                 
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	len[0] = 3;
	for ( int i = 1 ; i <= n ; i ++ )
	{
		len[i] = 2 * len[i-1] + i + 3;
		if ( len[i] > n ) { pos = i; break; }
	}
	cout << solve(n,pos) << endl;
	return 0;
}

P1911 L 国的战斗之排兵布阵

对于一个 \(2^n*2^n\) 长度的区间 我们发现 如果有一个点不能覆盖 那么一定可以通过 \(L\) 的形状来覆盖整片区间

那么我们可以考虑将大矩形分成四个小矩形 对于没有指挥部的三个区间 我们人为地构建出来一个 \(L\) 形指挥所来覆盖中间的三个矩形 这样每一个矩形都有一个指挥所了

分治代码实在是恶心人

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 2e3 + 5;

int read()
{
	int f = 1 , x = 0;
	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 n , x , y , tot , a[N][N] , vis[N*N];

void solve ( int lx , int ly , int rx , int ry , int posx , int posy )
{
	if ( rx - lx == 1 )
	{
		++ tot;
		for ( int i = lx ; i <= rx ; i ++ ) for ( int j = ly ; j <= ry ; j ++ ) if ( ! ( i == posx && j == posy ) ) a[i][j] = tot;
		return;
	}
	int midx = lx + rx >> 1 , midy = ly + ry >> 1 , nx , ny , pd = 0;
	if ( posx <= midx && posy <= midy ) pd = 1 , nx = midx , ny = midy;
	if ( posx > midx && posy <= midy ) pd = 2 , nx = midx + 1 , ny = midy;
	if ( posx <= midx && posy > midy ) pd = 3 , nx = midx , ny = midy + 1;
	if ( posx > midx && posy > midy ) pd = 4 , nx = midx + 1 , ny = midy + 1;
	++ tot;
	for ( int i = midx ; i <= midx + 1 ; i ++ ) for ( int j = midy ; j <= midy + 1 ; j ++ ) if ( ! ( i == nx && j == ny ) ) a[i][j] = tot;
	solve ( lx , ly , midx , midy , pd == 1 ? posx : midx , pd == 1 ? posy : midy );
	solve ( midx + 1 , ly , rx , midy , pd == 2 ? posx : midx + 1 , pd == 2 ? posy : midy );
	solve ( lx , midy + 1 , midx , ry , pd == 3 ? posx : midx , pd == 3 ? posy : midy + 1 );
	solve ( midx + 1 , midy + 1 , rx , ry , pd == 4 ? posx : midx + 1 , pd == 4 ? posy : midy + 1 );
}

signed main ()                                                 
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , x = read() , y = read();
	solve ( 1 , 1 , 1 << n , 1 << n , x , y );
	tot = 0;
	for ( int i = 1 ; i <= ( 1 << n ) ; i ++ , cout.put(endl) ) 
		for ( int j = 1 ; j <= ( 1 << n ) ; j ++ )
		{
			if ( i == x && j == y ) cout << 0 << ' ';
			else 
			{
				if ( !vis[a[i][j]] ) cout << ( vis[a[i][j]] = ++ tot ) << ' ';
				else cout << vis[a[i][j]] << ' ';
			}
		}
	return 0;
}

P1228 地毯填补问题

和上一道题基本一样 在转换的细节上需要注意一下

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 2e3 + 5;

int read()
{
	int f = 1 , x = 0;
	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 n , x , y , a[N][N] , vis[N*N];

struct node { int x , y , col; };

vector<node> vec;

void solve ( int lx , int ly , int rx , int ry , int posx , int posy )
{
	if ( rx - lx == 1 )
	{
		int pd = 0;
		if ( posx == lx && posy == ly ) pd = 1;
		if ( posx == lx && posy == ry ) pd = 2;
		if ( posx == rx && posy == ly ) pd = 3;
		if ( posx == rx && posy == ry ) pd = 4;
		for ( int i = lx ; i <= rx ; i ++ ) for ( int j = ly ; j <= ry ; j ++ ) if ( i != posx && j != posy ) vec.eb((node){i,j,pd});
		return;
	}
	int midx = lx + rx >> 1 , midy = ly + ry >> 1 , nx , ny , pd = 0;
	if ( posx <= midx && posy <= midy ) pd = 1 , nx = midx , ny = midy;
	if ( posx <= midx && posy > midy ) pd = 2 , nx = midx , ny = midy + 1;
	if ( posx > midx && posy <= midy ) pd = 3 , nx = midx + 1 , ny = midy;
	if ( posx > midx && posy > midy ) pd = 4 , nx = midx + 1 , ny = midy + 1;
	for ( int i = midx ; i <= midx + 1 ; i ++ ) for ( int j = midy ; j <= midy + 1 ; j ++ ) if ( i != nx && j != ny ) vec.eb((node){i,j,pd});
	solve ( lx , ly , midx , midy , pd == 1 ? posx : midx , pd == 1 ? posy : midy );
	solve ( lx , midy + 1 , midx , ry , pd == 2 ? posx : midx , pd == 2 ? posy : midy + 1 );
	solve ( midx + 1 , ly , rx , midy , pd == 3 ? posx : midx + 1 , pd == 3 ? posy : midy );
	solve ( midx + 1 , midy + 1 , rx , ry , pd == 4 ? posx : midx + 1 , pd == 4 ? posy : midy + 1 );
}

signed main ()                                                 
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , x = read() , y = read();
	solve ( 1 , 1 , 1 << n , 1 << n , x , y );
	for ( auto p : vec ) cout << p.x << ' ' << p.y << ' ' << p.col << endl;
	return 0;
}

P4141 消失之物

两种做法

  1. \(O(nmlogn)\)

    考虑分治 每次将右区间的贡献加进去 递归求左区间答案 当区间内只剩一个数的时候直接输出整个数组即可

  2. \(O(nm)\)

    我们将 \(f\) 数组求出来之后 对于每一个物品 设一个 \(g[j]\) 数组表示 处理该物品的时候 容积为 \(j\) 的方案数

    那么我们从 \(1\) 推到 \(m\) 边处理边输出即可

\(O(nmlogn)\)

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define int long long
#define inl inline
const int N = 2e3 + 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 n , m , dp[N] , w[N];

void solve ( int l , int r )
{
	if ( l == r ) //因为是一直向左递归 所以是有序的 
	{
		for ( int i = 1 ; i <= m ; i ++ ) printf ( "%d" , dp[i] % 10 );
		putchar('\n'); return;
	}
	
	int tmp[N]; //复制了一份上一层的dp数组 来还原初始状态 
	
	//将右面加入背包 处理左面
	memcpy ( tmp , dp , sizeof dp );
	for ( int i = mid + 1 ; i <= r ; i ++ )
		for ( int j = m ; j >= w[i] ; j -- )
			dp[j] = ( dp[j] + dp[j-w[i]] ) % 10;
	solve ( l , mid );
	
	//还原 并将左面加入背包 处理右面 
	memcpy ( dp , tmp , sizeof dp );
	for ( int i = l ; i <= mid ; i ++ )
		for ( int j = m ; j >= w[i] ; j -- )
			dp[j] = ( dp[j] + dp[j-w[i]] ) % 10;
	solve ( mid + 1 , r );
}

signed main ()
{
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) w[i] = read();
	dp[0] = 1 , solve ( 1 , n );
	return 0;
}

\(O(nm)\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 2e3 + 5;

int read()
{
	int f = 1 , x = 0;
	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 n , m , f[N] , g[N] , w[N];

signed main ()                                                 
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	f[0] = g[0] = 1;
	for ( int i = 1 ; i <= n ; i ++ ) w[i] = read();
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = m ; j >= w[i] ; j -- )
			( f[j] += f[j-w[i]] ) %= 10;
	for ( int i = 1 ; i <= n ; i ++ , cout.put(endl) )
	{
		for ( int i = 1 ; i <= m ; i ++ ) g[i] = f[i];
		for ( int j = 1 ; j <= m ; j ++ )
		{
			if ( j >= w[i] ) ( ( g[j] -= g[j-w[i]] ) += 10 ) %= 10;
			cout << g[j];
		}
	}
	return 0;
}

P1498 南蛮图腾

考虑对于每一个图形 记录左上角的那个 '/' 来处理 用 \(mp\) 数组记录字符情况

对于一层 有三个子三角形 我们分别递归处理即可 如果递归到只有一层(最基本的结构) 那么计入答案即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 2e3 + 5;

int read()
{
	int f = 1 , x = 0;
	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 n;
char mp[N][N];

void solve ( int x , int y , int dep )//左上角的 '/' 的位置
{
	if ( dep == 1 )
	{
		mp[x][y] = '/';
		mp[x][y+1] = '\\';
		mp[x+1][y-1] = '/';
		mp[x+1][y+2] = '\\';
		mp[x+1][y] = '_';
		mp[x+1][y+1] = '_';
		return;
	}
	solve ( x , y , dep - 1 );
	solve ( x + ( 1 << dep - 1 ) , y - ( 1 << dep - 1 ) , dep - 1 );
	solve ( x + ( 1 << dep - 1 ) , y + ( 1 << dep - 1 ) , dep - 1 );
}

signed main ()                                                 
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= ( 1 << n ) ; i ++ )
		for ( int j = 1 ; j <= ( 1 << n + 1 ) ; j ++ )
			mp[i][j] = ' ';
	solve ( 1 , 1 << n , n );
	for ( int i = 1 ; i <= ( 1 << n ) ; i ++ , cout.put(endl) ) 
		for ( int j = 1 ; j <= ( 1 << n + 1 ) ; j ++ )
			cout << mp[i][j];
	return 0;
}

P3612 [USACO17JAN] Secret Cow Code S

分治的思想 也是将一个区间分成三段来定位 设当前的区间中点为 \(temp\)

如果 \(pos\le temp\) 那么显然是不用管的 如果 \(pos=temp+1\) 那么是 \(temp\) 否则为 \(pos-temp-1\) 这样每次减半地求出和 \(pos\) 字母相等的位置即可

如果减到和原来序列一样就输出对应的字母

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
#define int long long

int read()
{
	int f = 1 , x = 0;
	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 pos , temp , len;
string s;

signed main ()                                                 
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	cin >> s , pos = read();
	temp = len = s.size() , s = " " + s;
	while ( temp < pos ) temp <<= 1;
	while ( temp != len ) temp >>= 1 , pos = pos > temp ? ( pos == temp + 1 ? temp : pos - temp - 1 ) : pos;
	cout << s[pos] << endl;
	return 0;
}

P1928 外星密码

写了一个 \(UB\) 递归函数一定需要有对应类型的返回值 比如说 \(string\) 类型的函数中的 \(return\ s\)

不能是在搜索到子串为 "]" 的时候才 \(return\) 这样会导致最外层最后一个字符不是括号的时候的 \(UB\)

对于本题 考虑递归处理压缩 对于每一个 "[" 先 \(cin\) 读入压缩个数 再递归处理后面的串并复制 \(k\) 次 接在答案末尾

重复这个过程 直到 "]" 出现或者读不进来字符

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl

int read()
{
	int f = 1 , x = 0;
	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;
}

string s;

string solve()
{
	int cnt;
	char ch;
	string s , temp;
	while ( cin >> ch )
	{
		if ( ch == '[' )
		{
			cin >> cnt;
			temp = solve();
			while ( cnt -- ) s = s + temp;
		}
		else if ( ch == ']' ) break;
		else s = s + ch;
	}
    return s;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	cout << solve();
	return 0;
}

P7579 「RdOI R2」称重(weigh)

二分,但是 \(99pts\)

对于奇数长度区间和偶数长度区间分别讨论:

  • 对于偶数长度区间,显然要分治查询左右两个区间,如果不相等,说明两个都在值小的区间内,否则,说明一边一个。

  • 当我们的区间长度为奇数的时候,在偶数长度的基础上,\(mid\) 节点是不必要查找的,只需要在递归下去的时候带上 \(mid\) 即可。

    而且当返回值为两区间相等的时候,如果当前区间内只有一个球,那么必定是 \(mid\),否则需要递归处理两个小区间

#include <bits/stdc++.h>
using namespace std;
// #define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
const int N = 3e5 + 5;

int read()
{
	int f = 1 , x = 0;
	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 n;

char query ( int xl , int xr , int yl , int yr )
{
    cout << "1 " << xr - xl + 1 << ' ';
    for ( int i = xl ; i <= xr ; i ++ ) cout << i << ' ';
    cout << yr - yl + 1 << ' ';
    for ( int i = yl ; i <= yr ; i ++ ) cout << i << ' ';
    cout << endl;
    char ch; cin >> ch;
    return ch;
}

vector<int> ans;

void solve ( int l , int r , int cnt )
{
    if ( l == r && cnt == 1 ) return ans.eb(l) , void();
    int mid = l + r >> 1;
    if ( ( r - l + 1 ) % 2 ) //奇数区间
    {
        char op = query ( l , mid - 1 , mid + 1 , r );
        if ( op == '<' ) solve ( l , mid , cnt );
        else if ( op == '>' ) solve ( mid , r , cnt );
        else
        {
            if ( cnt == 1 ) solve ( mid , mid , 1 );
            else solve ( l , mid - 1 , 1 ) , solve ( mid + 1 , r , 1 );
        }
    }
    else //偶数区间
    {
        char op = query ( l , mid , mid + 1 , r );
        if ( op == '<' ) solve ( l , mid , cnt );
        else if ( op == '>' ) solve ( mid + 1 , r , cnt );
        else solve ( l , mid , 1 ) , solve ( mid + 1 , r , 1 );
    }
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
    n = read();
    solve ( 1 , n , 2 );
    sort ( ans.begin() , ans.end() );
    cout << "2 " << ans[0] << ' ' << ans[1] << endl;
    return 0;
}

Sum

分治背包。

考虑最简单的 \(O(n^2k)\) 的暴力,我们设 \(f[j]\) 表示选的数的数量为 \(j\) 的时候的背包最大权值。

枚举每一个数组,容积和这个数组选到第几个数来进行 \(dp\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define eb emplace_back
#define pii pair<int,int>
#define int long long
const int N = 3000 + 5;

int read()
{
	int f = 1 , x = 0;
	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 n , k , sz[N] , a[N][N] , f[N];
vector<int> sum[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
    n = read() , k = read();
    for ( int i = 1 ; i <= n ; i ++ )
    {
        sz[i] = read() , sum[i].eb(0);
        for ( int j = 1 ; j <= sz[i] ; j ++ ) sum[i].eb(sum[i][j-1]+read());
    }
    for ( int i = 1 ; i <= n ; i ++ ) 
        for ( int j = k ; j >= 0 ; j -- )
            for ( int l = min ( sz[i] , j ) ; l >= 0 ; l -- )
                f[j] = max ( f[j] , f[j-l] + sum[i][l] );
    cout << f[k] << endl;
    return 0;
}

考虑优化。

一个结论:最优取数状态一定是一些取整体的数组,再加上一些一个都不取的数组,再加上一个取了一部分的数组。

证明:假设最后有两个数组 \(a\)\(b\),分别选了 \(cnta\)\(cntb\) 个且都没选满,那么如果 \(a[cnta]\le b[cntb]\),那么我们减小 \(cnta\) 直到 \(b\) 数组选满是更优,同理,如果 \(a[cnta]>b[cntb]\),那么我们直接减小 \(cntb\) 是更优的做法。

那么我们将每一段序列看成一个物品,然后做 \(01\) 背包,分治到最后一个点的时候暴力枚举整个数组进行这个取一部分的数组的考虑。

时间复杂度 \(O(nk\log n)\)


posted @ 2023-11-02 23:08  Echo_Long  阅读(8)  评论(0编辑  收藏  举报