算法競賽進階指南0x 遞歸與遞推

知識點

遞歸:當問題存在,C->B->A這樣的拓撲序的情況下,我們即可使用遞歸,把A直接丟給遞歸,在處理A之前先處理B,在處理B之前先處理C

遞推:和遞歸相反,遞推先處理C再B最後A

遞歸實現指數型枚舉

從 1~n 這 n 個整數中隨機選取任意多個,輸出所有可能的選擇方案。

輸入格式
輸入一個整數n。

輸出格式
每行輸出一種方案。

同一行內的數必須升序排列,相鄰兩個數用恰好1個空格隔開。

對於沒有選任何數的方案,輸出空行。

本題有自定義校驗器(SPJ),各行(不同方案)之間的順序任意。

數據範圍
1≤n≤15
輸入樣例:
3
輸出樣例:

3
2
2 3
1
1 3
1 2
1 2 3

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

int n;

void dfs(int pos,int k)
{
    if(k==n)
    {
        for(int i=0;i<n;i++)
            if(pos>>i&1)
                cout<<i+1<<" ";
        puts("");
        return;
    }   
    dfs(pos,k+1);//不選
    dfs(pos|(1<<k),k+1);//選
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    cin>>n;
    dfs(0,0);


    return 0;
}

這題沒什麼好說的,基操~ . ~

遞歸實現組合型枚舉

從 1~n 這 n 個整數中隨機選出 m 個,輸出所有可能的選擇方案。

輸入格式
兩個整數 n,m ,在同一行用空格隔開。

輸出格式
按照從小到大的順序輸出所有方案,每行1個。

首先,同一行內的數升序排列,相鄰兩個數用一個空格隔開。

其次,對於兩個不同的行,對應下標的數一一比較,字典序較小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

數據範圍
n>0 ,
0≤m≤n ,
n+(n−m)≤25
輸入樣例:
5 3
輸出樣例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5

這題就是從上題代碼,再剪枝就行了,來看代碼。

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

int n,m;

void dfs(int pos,int k,int cnt)
{
    if(cnt>m||(cnt+(n-k)<m)) return ;
    if(k==n)
    {
        for(int i=0;i<n;i++)
            if(pos>>i&1)
                cout<<i+1<<" ";
        puts("");
        return;
    }   
    dfs(pos|(1<<k),k+1,cnt+1);//選
    dfs(pos,k+1,cnt);//不選
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    cin>>n>>m;
    dfs(0,0,0);


    return 0;
}

遞歸實現排列型枚舉

把 1~n 這 n 個整數排成一行後隨機打亂順序,輸出所有可能的次序。

輸入格式
一個整數n。

輸出格式
按照從小到大的順序輸出所有方案,每行1個。

首先,同一行相鄰兩個數用一個空格隔開。

其次,對於兩個不同的行,對應下標的數一一比較,字典序較小的排在前面。

數據範圍
1≤n≤9
輸入樣例:
3
輸出樣例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

這題呢,我推薦大家調用庫(大佬請無視)

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=15;

int a[N];

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) a[i]=i;
    do{
        for(int i=1;i<=n;i++) cout<<a[i]<<" ";
        puts("");
    }while(next_permutation(a+1,a+n+1));



    return 0;
}

費解的開關

你玩過“拉燈”遊戲嗎?25盞燈排成一個5x5的方形。每一個燈都有一個開關,遊戲者可以改變它的狀態。每一步,遊戲者可以改變某一個燈的狀態。遊戲者改變一個燈的狀態會產生連鎖反應:和這個燈上下左右相鄰的燈也要相應地改變其狀態。

我們用數字“1”表示一盞開着的燈,用數字“0”表示關着的燈。下面這種狀態

10111
01101
10111
10000
11011
在改變了最左上角的燈的狀態後將變成:

01111
11101
10111
10000
11011
再改變它正中間的燈後狀態將變成:

01111
11001
11001
10100
11011
給定一些遊戲的初始狀態,編寫程序判斷遊戲者是否可能在6步以內使所有的燈都變亮。

輸入格式
第一行輸入正整數n,代表數據中共有n個待解決的遊戲初始狀態。

以下若干行數據分爲n組,每組數據有5行,每行5個字符。每組數據描述了一個遊戲的初始狀態。各組數據間用一個空行分隔。

輸出格式
一共輸出n行數據,每行有一個小於等於6的整數,它表示對於輸入數據中對應的遊戲狀態最少需要幾步才能使所有燈變亮。

對於某一個遊戲初始狀態,若6步以內無法使所有燈變亮,則輸出“-1”。

數據範圍
0<n≤500
輸入樣例:
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111
輸出樣例:

3
2
-1

來看代碼

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=31;

char g[N][N],t[N][N];
int dx[5]={-1,1,0,0,0},dy[5]={0,0,-1,1,0};

void touch(int x,int y){
    for(int i=0;i<5;i++)
    {
        int xx=x+dx[i],yy=y+dy[i];
        g[xx][yy]^=1;
    }

}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int T;
    cin>>T;
    while(T--)
    {
        for(int i=1;i<=5;i++)
            scanf("%s",g[i]+1);
        int res=7;
        memcpy(t,g,sizeof t);
        for(int i=0;i<1<<5;i++)
        {
            memcpy(g,t,sizeof g);
            int cnt=0;
            for(int k=0;k<5;k++)
                if(i>>k&1) {
                    touch(1,k+1);
                    cnt++;
                }
            for(int j=2;j<=5;j++)
                for(int k=1;k<=5;k++)
                {
                    if(g[j-1][k]=='0') {
                        touch(j,k);
                        cnt++;
                    }
                }
            bool flag=true;
            for(int j=1;j<=5;j++)
                if(g[5][j]=='0') flag=false;
            if(flag) res=min(res,cnt);
        }
        if(res>6) puts("-1");
        else cout<<res<<endl;
    }


    return 0;
}

我們通過題目可以發現,想要使上面一層的燈必須要按下面的開關,唯獨第一行不同,我們用指數型枚舉,暴力枚舉一下第一行,如果最終,最後一行全部被關上則方案可行,否則方案不可行,記錄每一次開關燈的次數即可完成此題。

奇怪的漢諾塔

漢諾塔問題,條件如下:

1、這裏有A、B、C和D四座塔。

2、這裏有n個圓盤,n的數量是恆定的。

3、每個圓盤的尺寸都不相同。

4、所有的圓盤在開始時都堆疊在塔A上,且圓盤尺寸從塔頂到塔底逐漸增大。

5、我們需要將所有的圓盤都從塔A轉移到塔D上。

6、每次可以移動一個圓盤,當塔爲空塔或者塔頂圓盤尺寸大於被移動圓盤時,可將圓盤移至這座塔上。

請你求出將所有圓盤從塔A移動到塔D,所需的最小移動次數是多少。

輸入格式
沒有輸入

輸出格式
對於每一個整數n(1≤n≤12),輸出一個滿足條件的最小移動次數,每個結果佔一行。

輸入樣例:
沒有輸入
輸出樣例:
參考輸出格式

我們將三柱轉化爲四柱,四柱就是枚舉一下將1-n-1個盤子放在輔助柱上,轉化爲三柱去做,然後看能不能優化,非常簡單,來看代碼。

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

int f[13],g[13];

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    for(int i=1;i<=12;i++) f[i]=(1<<i)-1;
    memset(g,0x3f,sizeof g);
    g[1]=1;
    g[0]=0;
    cout<<g[1]<<endl;
    for(int i=2;i<=12;i++)
    {
        for(int j=0;j<i;j++)
        {
            g[i]=min(g[i],g[j]+f[i-j]+g[j]);
        }
        cout<<g[i]<<endl;
    }

    return 0;
}

約數之和

假設現在有兩個自然數A和B,S是AB的所有約數之和。

請你求出S mod 9901的值是多少。

輸入格式
在一行中輸入用空格隔開的兩個整數A和B。

輸出格式
輸出一個整數,代表S mod 9901的值。

數據範圍
0≤A,B≤5×107
輸入樣例:
2 3
輸出樣例:
15
注意: A和B不會同時爲0。

我們知道約數之和公式爲(2n1)(3n2)(5n3)\sum(2^{n1})*\sum(3^{n2})*\sum(5^{n3})\cdots
每一項都是拆開質因數後的等比級數。我們可以用等比數列求和公式,但是不適用於這題,因爲取模運算只對加減乘有交換律,我們不希望出現除號,當然我們也不可能一個個加過去,必然會超時。那怎麼辦呢?這題實際上是要求我們,模擬一下等比數列求和公式的推導流程。
1+q2+q3++qn1+q^2+q^3+\cdots+q^n
當n時奇數時:
(1+q2+q3++qn/2)+(qn/2+1+qn/2+2+qn/2+3++qn)(1+q^2+q^3+\cdots+q^{n/2}) + (q^{n/2+1}+q^{n/2+2}+q^{n/2+3}+\cdots+q^n)
我們繼續對上式化簡:
(1+q2+q3++qn/2)+qn/21+q2+q3++qn/2(1+q^2+q^3+\cdots+q^{n/2}) + q^n/2*(1+q^2+q^3+\cdots+q^{n/2})
再化簡
(qn/2+1)(1+q2+q3++qn/2)(q^n/2+1) (1+q^2+q^3+\cdots+q^{n/2})
我們將範圍放縮了一般
當n是偶數時:
我們把他變爲奇數就可以了。
我們來看代碼:

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int mod=9901;

int qui(int a,int b)
{
    int res=1;
    a=a%mod;
    while(b)
    {
        if(b&1) res=(res*a)%mod;
        b>>=1;
        a=(a*a)%mod;
    }
    return res;
}

int sum(int a,int b)
{
    if(b==0) return 1;
    if(b&1) return ((qui(a,(b+1)/2)+1)*sum(a,(b-1)/2))%mod; 
    return (a%mod*sum(a,b-1)+1)%mod;
}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int a,b;
    cin>>a>>b;
    if(a==0)
        cout<<0<<endl;
    else
    {
        int ans=1;
        for(int i=2;a!=1;i++)
        {
            int cnt=0;
            while(a%i==0)
            {
                a/=i;
                cnt++;
            }
            if(cnt) ans=(ans*sum(i,cnt*b))%mod; 
        }    
        cout<<ans<<endl;
    }

    return 0;
}

分形之城

城市的規劃在城市建設中是個大問題。

不幸的是,很多城市在開始建設的時候並沒有很好的規劃,城市規模擴大之後規劃不合理的問題就開始顯現。

而這座名爲 Fractal 的城市設想了這樣的一個規劃方案,如下圖所示:

當城區規模擴大之後,Fractal 的解決方案是把和原來城區結構一樣的區域按照圖中的方式建設在城市周圍,提升城市的等級。

對於任意等級的城市,我們把正方形街區從左上角開始按照道路標號。

雖然這個方案很爛,Fractal 規劃部門的人員還是想知道,如果城市發展到了等級 N,編號爲 A 和 B 的兩個街區的直線距離是多少。

街區的距離指的是街區的中心點之間的距離,每個街區都是邊長爲 10 米的正方形。

輸入格式
第一行輸入正整數n,表示測試數據的數目。

以下n行,輸入n組測試數據,每組一行。

每組數據包括三個整數 N,A,B, 表示城市等級以及兩個街區的編號,整數之間用空格隔開。

輸出格式
一共輸出n行數據,每行對應一組測試數據的輸出結果,結果四捨五入到整數。

數據範圍
1≤N≤31,
1≤A,B≤22N,
1≤n≤1000
輸入樣例:
3
1 1 2
2 16 1
3 4 33
輸出樣例:
10
30
50

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>

#define ll long long
#define pll pair<ll,ll>

using namespace std;

pll work(ll n,ll k){
    if(n==0) return {0,0};
    ll p=1ll<<(2*n-2);
    ll d=1ll<<(n-1);  
    pll temp=work(n-1,k%p);
    ll res=k/p;
    if(res==0) return {temp.second,temp.first};
    else if(res==1) return {temp.first,temp.second+d};
    else if(res==2) return {temp.first+d,temp.second+d};
    else return {2*d-temp.second-1,d-1-temp.first};

}


int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int T;
    cin>>T;
    while(T--)
    {
        ll n,a,b;
        cin>>n>>a>>b;
        pll t1=work(n,a-1);
        pll t2=work(n,b-1);
        double d1=t1.first-t2.first;
        double d2=t1.second-t2.second;
        double res=sqrt(d1*d1+d2*d2)*10;
        printf("%.0lf\n",res);
    }


    return 0;
}

這題的難度非常大,我來給大家拆解一下,遞歸找點,work(n,k),n帶表第n個城市,k代表城市編號。我們的遞歸思路就是,找到上一級演化前的城市。然後對演化前的城市進行旋轉運算costsintsintcost \begin{matrix} cost & -sint \\ sint & cost \end{matrix}
找到上一級城市後,對其進行運算旋轉運算,第一塊旋轉90,這個比較簡單,(x,y)變成(-y,x)做一個邊界處理之後就是(y,x)
第二塊和第三塊比較簡單,不講了
第四塊轉化爲(y,-x) 做一個邊界處理(2d-y-1,d-x-1)
然後得到座標後,把距離算出來,大功告成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章