暑假集訓test6

喜聞樂見,test續集。
來上題。

1.Hello

題目描述

Alice 和 Bob 有一個長度爲2n的數。現在他們要在這個數字上玩遊戲。他們分別要從 2n 個位中取出 n 個位組成自己的幸運值。每一回合,Alice 或 Bob 把數字最左邊的那一位拿來放在自己幸運值的最末位。在第 i 輪操作過後,被選取的數位(原數的第i位)會從原數中消失。現在 Alice 和 Bob 想要使得他們兩個幸運值的和儘可能大。請求出這個值。

輸入格式

第一行一個整數 n ,意爲幸運值的長度。
第二行一個長度爲 2n 的數字,表示原數。

輸出格式

一個整數爲幸運值的和的最大值。

樣例數據

輸入
2
1234

輸出
46

備註

【樣例說明】
Alice 取 1、2 位。Bob 取 3、4 位。幸運值分別爲 12 和 34 ,和爲 46 。

【數據範圍】
對 30% 的輸入數據 : n≤10;
對 100% 的輸入數據 : n≤18,原數與幸運值均允許前綴 0 的存在。

用動歸。
對於一個人的倒着數第i位對於最終答案的貢獻爲a[i]*10^i。
f[i][j]表示Alice取了前i位,Bob取了前j位的狀態下,ans的最大值。
f[i][j]可以轉移到f[i][j+1]或者f[i+1][j],最終輸出f[n][n]。
如下。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;

int n;
long long b[101],v[21],f[21][21],pre[21][21];
char a[101];

int main()
{
    //freopen("hello.in","r",stdin);
    //freopen("hello.out","w",stdout);
    memset(f,255,sizeof(f));

    scanf("%d%s",&n,a+1);
    v[0]=1;
    f[0][0]=0;
    for(int i=1;i<=18;i++)
        v[i]=v[i-1]*10;        //對我就是貢獻的那個10^i
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
        {
            if(i!=n&&f[i][j]+v[n-i-1]*(a[i+j+1]-'0')>f[i+1][j])
            {
                f[i+1][j]=f[i][j]+v[n-i-1]*(a[i+j+1]-'0');
                pre[i+1][j]=0;
            }                                                   //兩種可能的狀態轉移方程。
            if(j!=n&&f[i][j]+v[n-1-j]*(a[i+j+1]-'0')>f[i][j+1])
            {
                f[i][j+1]=f[i][j]+v[n-1-j]*(a[i+j+1]-'0');
                pre[i][j+1]=1;
            }
        }
    cout<<f[n][n];
    return 0;   
}

2.Rect

題目描述

給定一個由數字(0-9)構成的字符串 s 。我們可以由此定義出 size(s) * size(s) 大小的矩陣 b ,其中 b[i][j] = s[i] * s[j] ;請問在這個矩陣 b 中,有多少子矩形滿足其中的 b[i][j] 的和爲另一個給定的數字 a 。

輸入格式

第一行一個整數 a 。
第二行字符串 s 。

輸出格式

輸出一個整數表示滿足條件的子矩形數。

樣例數據

輸入
10
12345

輸出
6

備註

【樣例說明】
b矩陣爲:
01 02 03 04 05
02 04 06 08 10
03 06 09 12 15
04 08 12 16 20
05 10 15 20 25

和爲10的子矩形有:

(1)
01 02 03 04

(2)
01
02
03
04

(3)
04 06

(4)
04
06

(5)
10

(6)
10

以上共六個。

【數據範圍】
對 10% 的輸入數據 :size(s)≤10 ;
對 30% 的輸入數據 :size(s)≤100 ;
對 100% 的輸入數據 :0≤a≤1000000000,size(s)≤4000 。

由於b[i][j] = s[i] * s[j],則矩陣的第一行和第一列爲原序列,且下面所有矩陣的和都是第一行子矩陣的和的倍數,也就是第一行的子矩陣和第一列的子矩陣的乘積。即要枚舉出符合提議的矩陣,應先找第一行的數,然後用找符合a/sum[x1…x2](指第一行從x1到x2的和)=sum[y1…y2](指第一列從y1到y2的和)的子矩陣的值分別有多少個,再把兩個數乘起來累加。
但如果a=0,則上述方法不成立,當sum[x1…x2]=0時,sum[y1…y2]可以取任何值。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;

int n,len;
int a[45000],cnt[45000];
long long ans;
char s[45000];

int main()
{
    //freopen("rect.in","r",stdin);
    //freopen("rect.out","w",stdout);

    scanf("%d%s",&n,s+1);
    len=strlen(s+1);
    for(int i=1;i<=len;i++)
        a[i]=a[i-1]+s[i]-'0';      //第一排各種各樣的和
    for(int i=1;i<=len;i++)
        for(int j=i;j<=len;j++)
            cnt[a[j]-a[i-1]]++;    //每種因數出現次數
    if(n!=0)
    {
        for(int i=1;i<=len;i++)
            for(int j=i;j<=len;j++)
            {
                int t=a[j]-a[i-1];
                if(t==0)
                    continue;
                if(n%t||(n/t)>45000)
                    continue;
                ans+=cnt[n/t];
            }
    }       
    else
    {
        for(int i=1;i<=len;i++)
            for(int j=i;j<=len;j++)
            {
                int t=a[j]-a[i-1];
                if(t==0)
                    ans+=len*(len+1)/2;     //相乘
                else
                    ans+=cnt[0];
            }
    }
    cout<<ans<<endl;
    return 0;
}

3.Shortest

題目描述

給定一張 n 個點的有向帶權完全圖,和一個數組 a[] ,請按順序刪除數組中的點,請求出在刪除點 a[i] 以前,所有未刪除點對之間的最短路的值的和。

輸入格式

第一行一個整數 n ,表示點數;
接下來 n 行,每行 n 個數構成鄰接矩陣,描述每條邊的權值,保證 i 號點到 i 號點的權值爲 0 ;
最後一行 n 個小於等於 n 的不同的數,描述數組 a[i]。

輸出格式

輸出 1 行 n 個數,表示答案。

樣例數據

輸入
4
0 3 1 1
6 0 400 1
2 4 0 1
1 1 1 0
4 1 2 3

輸出
17 23 404 0

備註

【數據範圍】
對 30% 的輸入數據 :1≤n≤10 ;
對 100% 的輸入數據 :1≤n≤500;0<權值≤100000 。

如果用最短路的話是會超時的呦,不論是Dijkstra還是SPFA。
這是考試時血與淚的教訓,一定要記得算時間複雜度和空間複雜度。
當然我們可以換種方式考慮。
把刪點的行爲看作是逆向加點,即往裏面不斷加點並更新答案。
可以用Floyed的思想。
因爲並不是每個點的最短路都會更新,一共只有三種情況。
一是所加點到已加的所有點的路徑的更新。
二是已加的所有點到所加點的路徑的更新。
三是已加點到已加點中途經過所加點的路徑的更新。
如代碼。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
using namespace std;

int n;
int f[510][510],a[510];
long long ans[510];

int main()
{
    //freopen("shortest.in","r",stdin);
    //freopen("shortest.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>f[i][j];
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=n;i;i--)
    {
        for(int j=n;j>i;j--)
            for(int k=n;k>i;k--)
            {
                f[a[i]][a[j]]=min(f[a[i]][a[j]],f[a[i]][a[k]]+f[a[k]][a[j]]);    //第一種情況
                f[a[j]][a[i]]=min(f[a[j]][a[i]],f[a[j]][a[k]]+f[a[k]][a[i]]);    //再反着來一遍,第二種情況
            }
        for(int j=n;j>i;j--)
            for(int k=n;k>i;k--)
                f[a[j]][a[k]]=min(f[a[j]][a[k]],f[a[j]][a[i]]+f[a[i]][a[k]]);     //所加點爲中途的點,第三種情況
        for(int j=n;j>=i;j--)
            for(int k=n;k>=i;k--)
                ans[i]+=f[a[j]][a[k]];
    }
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<" ";
    return 0;
}

嗯,就是這樣。
來自2017.7.15

——我認爲return 0,是一個時代的終結。

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