1. 程式人生 > >NOIP2018提高組模擬題(五)

NOIP2018提高組模擬題(五)

一個 con mat space 特殊 pen prime pri print

字符串(string)

Description

小林與亮亮正在做一個遊戲。小林隨意地寫出一個字符串,字符串只由大寫 字母組成,然後指定一個非負整數 m,亮亮可以進行至多 m 次操作,每次操作 為交換相鄰兩個字符。亮亮的目標是使得操作後的字符串出現最長相同的字符的 長度最大。你能幫亮亮計算一下這個最大長度是多少嗎?

Input

第一行一個字符串 S。 第二行一個整數 m。

Output

只有一個整數,表示所求的最大長度。

表示剛開始想了一個小時的\(DP\)

然後還出樣例了,

要不是手出了一組樣例就涼了

然後還有20分鐘的時候,發現是個貪心+遞歸

將兩側的向中間移動顯然更優.

容易發現,同種字母移動才會產生影響.

因此直接枚舉每種字母,記錄其位置.

由於從某一位置到目標位置的交換次數可求.所以這樣是可做的.

\(\color{red}官方題解\)表示沒看太懂

考察內容:字符串、枚舉與貪心

字符串長度不超過 50,我們就可以枚舉哪一個字符不動,其他相同字符向它靠近,左 右兩邊相同字符計算出它們與選定字符相鄰需要移動幾次,以此為關鍵值從小到大排序,貪心地處理即可。

代碼

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define R register
using namespace std;
inline void in(int &x)
{
    int f=1;x=0;char s=getchar();
    while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
    while(isdigit(s)){x=x*10+s-'0';s=getchar();}
    x*=f;
}
int ans,m,n;
int pos[833];
char s[833];
inline int cs(char s)
{
    return s-'A'+1;
}
inline int calc(int l,int r)
{
    if(l==r)return 0;
    if(l==r-1)return pos[r]-pos[l]-1;
    return calc(l+1,r-1)+pos[r]-pos[l]-(r-l);
}
int main()
{
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    scanf("%s",s+1);in(m);n=strlen(s+1);
    for(R int i=1;i<=26;i++)
    {
        int tmp=0;
        for(R int j=1;j<=n;j++)
            if(cs(s[j])==i)pos[++tmp]=j;
        if(tmp==1){ans=max(ans,1);continue;}
        int now=-2147483644;
        for(R int j=1;j<=tmp;j++)
            for(R int l=j+1;l<=tmp;l++)
                if(calc(j,l)<=m)
                    now=max(now,l-j+1);
        ans=max(ans,now);
    }
    printf("%d",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

桃園之禮(peach)

Description

小林和亮亮在桃園裏一起玩遊戲。桃園裏的桃樹成行成列,剛好構成一個 N ×M 的矩陣,亮亮在某些桃樹下放置了一些小禮物,要求小林把所有樹下的禮 物全部收集起來。 小林從左上角的桃樹(1,1)出發,走到右下角的桃樹(N,M)。他 只能沿著路徑向下或者向右走,某些桃樹下有禮物,他必須到達所有有禮物的樹 下並把禮物收集起來。 小林在出發前,想請你幫他計算一下,他有多少種不同的 走法。由於答案可能很大,你只需要輸出答案模 100000000(10^8)後的值即可。

Input

第一行三個整數 N,M 和 K。 N,M 表示矩陣的大小, K 表示有禮物的桃樹的 棵數。

接下來 k 行,每行兩個整數 X,Y,表示一棵有禮物的桃樹的坐標(X,Y)

Output

只有一個整數,表示不同的走法數模 100000000 後的值。

剛開始沒看懂題.

首先不考慮中間的有禮物的樹.

我們就是求解從\((1,1)\)\((n,m)\)的走法.

有這麽多種
\[ C_{n+m-2}^{m-1} \]

因為我們只可能會走\(n+m-2\)步,而我們必須要選擇\(m-1\)步走橫向.

\(m-1\)步隨便選.即\(C_{n+m-2}^{m-1}\)

當然也可以是
\[ C_{n+m-2}^{n-1} \]
然後就是將大的矩形拆成小的矩形了.,

比如,如果一些地方有禮物的樹.,我們就可以拆成這些子矩形。

技術分享圖片

然後我們求解子矩形從左上角到右下角的方案數.

根據乘法原理累乘即可.

註意判無解的情況!!!(被卡了\(10pts\))

這題惡心就惡心在模數不是一個質數.

我們可以考慮質因數分解來求解.

這裏就不過多解釋如何質因數分解了,相信大家能看懂代碼的.

\(\color{red}官方題解\)

考察算法:質因數分解(或逆元+中國剩余定理)、排列組合

? 根據乘法原理,問題可以分解為求在一個 n*m 的矩形中,從左上角走到右下角共有幾 種走法。顯然答案為 \(C_{n+m-2}^{m-1}\) 。我們既可以用分解質因數的方法來計算,也可以采用求逆元 與中國剩余定理的方法來處理除法

代碼

#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 10008
#define mod 100000000
#define R register
#define int long long 
using namespace std;
inline void in(int &x)
{
    int f=1;x=0;char s=getchar();
    while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
    while(isdigit(s)){x=x*10+s-'0';s=getchar();}
    x*=f;
}
int n,m,k,ans=1,cur;
struct cod{int x,y;}t[10008];
bool vis[60008];
int prime[60008],cnt,f[60008];
inline void pri()
{
    for (R int i=2;i<=n+m;i++)
    {
        if (!vis[i]) prime[++cnt] = i;
        for (R int j=1;j<=cnt and i*prime[j]<=n+m;j++)
        {
            vis[i*prime[j]]=true;
            if (i%prime[j]==0) break;
        }
    }
}
inline int ksm(int x,int &y)//這裏加&,可以達到memset的效果. 
{
    R int res=1;
    for(;y;y>>=1,x=x*x%mod)
        if(y&1)res=res*x%mod;
    return res;
}
inline void calc(int x,int v)
{
    for (R int i=1;i<=cnt and prime[i]<=x;i++) 
    while(x%prime[i]==0)f[i]+=v,x/=prime[i];
    cur=cur*x%mod; 
}
inline int C(int x,int y)
{
    if (y>x) return 0;
    cur=1;
    for(R int i=x-y+1;i<=x;i++) calc(i,1);
    for(R int i=1;i<=y;i++) calc(i,-1);
    for(R int i=1;i<=cnt and prime[i]<=x;i++)
        (cur*=ksm(prime[i],f[i]))%=mod;
    return cur;
}
inline bool ccp(const cod&a,const cod&b)
{
    if(a.x==b.x)return a.y<b.y;
    return a.x<b.x;
}
signed main()
{
    freopen("peach.in","r",stdin);
    freopen("peach.out","w",stdout);
    in(n),in(m),in(k);pri();
    t[1].x=t[1].y=1;
    for(R int i=2;i<=k+1;i++)
        in(t[i].x),in(t[i].y);
    t[2+k].x=n,t[2+k].y=m;
    sort(t+1,t+k+3,ccp);
    for(R int i=2;i<=k+2;i++)
    {
        int nn=t[i].x-t[i-1].x+1,mm=t[i].y-t[i-1].y+1;
        if(nn<=0 or mm<=0)//判無解 
        {
            puts("0");
            return 0;
        }
        (ans*=C(nn+mm-2,mm-1))%=mod;
    }
    printf("%lld",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

魔法森林(forest)

Description

亮亮在夢中遊歷了魔法城堡後,對此心馳神往,於是用自己制造的法杖,創 造了一片魔法森林。

? 這片森林中一開始有 n 個節點,沒有邊相連,若想要在第 i 個點和第 j 個點 之間建立一條雙向通路,則需花 費 Cij 的魔法值。

? 每個結點上住著一個魔法居民,若兩個節點間有邊直接相連,則他們就成為 了鄰居。居民一共有三種類型:

? ①村民:他們只能通過道路拜訪自己的鄰居。

? ②巫師:他們可以拜訪自己的鄰居以及鄰居的鄰居。

? ③大魔法師:由於他們擁有法力,因此可以拜訪所有與自己連通的人。

? 亮亮不希望有人孤單,因此他保證了每種類型的居民要麽不出現,否則至少 出現兩個。同時,他又希望大家能建立良好的關系,所以他決定花費魔法值為魔 法森林修路,使得任意居民都可以拜訪其他所有的居民。

? 他想知道,最少需要建立多少條道路才能達成自己的心願。在道路數目最少 的前提下,花費的魔法值最小又是多少。

Input

第一行有一個整數 n。

第二行有 n 個整數, 第 i 個整數表示 i 號節點所居住的居民的類型(1 表示 村民, 2 表示巫師, 3 表示大魔法師) 。

接下來 n 行,每行 n 個數,是一個 n*n 的矩陣 Cij。數據保證 Cij = Cji, Cii=0。

Output

一行兩個整數, 分別表示最小道路數和最小魔法值。

這裏就不多BB了,官方題解寫的挺好.

\(\color{red}官方題解\)

考察算法:分類討論、圖論

①存在第一類點的情況

此時,顯然要從每個第一類點向其它點連邊。 我們發現這樣是滿足要求的,因為這樣一來,每個第二類點都可以通過第一步到達 一個第一類點來訪問所有點,並且整個圖顯然也是連通的。 直接模擬即可。

②只存在第三類點的情況

此時,目的是使所有點連通。也就是一個最小生成樹問題。 使用 \(Prim\)\(Kruskal\)\(O(N^2)\)\(O(N^2logN)\)中解決。

③其他情況

這就是說,存在第二類點,另外只可能存在第三類點。 我們取一個點,從這個點向其它點各連一條邊,構成一個 star。 我們發現 star 是一棵樹,且直徑≤2,所以是滿足要求的。 從而,第一問“最小邊數”的答案為 N-1。 下面將對第二類點的數目進行討論:

③a 恰兩個

此時,若選擇讓兩個第二類點成為鄰居,那麽其他的點可以在兩個第二類點中選擇一個費用小的連過去。 否則,這兩個點的距離=2,那麽就是說這兩個點有一個公共鄰居 u。顯然其他 的點都只能連到 u 上,這樣一來就構成了一個以 u 為中心的 star。枚舉所有 star 即可。

③b 超過兩個

由於邊數是 N-1,因此至少有一對第二類點不是鄰居。

這樣一來就成了③a的第二種情況,枚舉所有 star 即可。

最後的結論是:首先枚舉所有 star 取最小費用,然後如果第二類點的數目為 2, 再考慮這兩個點直接相連時的特殊情況即可。

代碼

#include<cstdio>
#include<iostream>
#include<algorithm>
#define R register
using namespace std;
inline void in(int &x)
{
    int f=1;x=0;char s=getchar();
    while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
    while(isdigit(s)){x=x*10+s-'0';s=getchar();}
    x*=f;
}
int n,v[2580],tot,f[2580],mnn=4;
bool flg;
int res[258][258];
struct cod{int u,v,w;}edge[80008];
inline bool ccp(const cod&a,const cod&b)
{
    return a.w<b.w;
}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
inline void mofashi()
{
    int cnt=0,ans=0;
    for(R int i=1;i<=n;i++)f[i]=i;
    for(R int i=1;i<=n;i++)
        for(R int j=1,x;j<=n;j++)
        {
            in(x);
            if(i>=j and x)
            {
                edge[++tot].u=i;
                edge[tot].v=j;
                edge[tot].w=x;
            }
        }
    sort(edge+1,edge+tot+1,ccp);
    for(R int i=1;i<=tot;i++)
    {
        R int u=edge[i].u,v=edge[i].v,w=edge[i].w;
        R int fu=find(u),fv=find(v);
        if(fu==fv)continue;
        cnt++;ans+=w;f[fu]=fv;
        if(cnt==n-1)break;
    }
    printf("%d\n%d\n",cnt,ans);
}
int main()
{
    freopen("forest.in","r",stdin);
    freopen("forest.out","w",stdout);
    in(n);
    for(R int i=1;i<=n;i++)in(v[i]),mnn=min(v[i],mnn);
    if(mnn==1)
    {
        for(R int i=1;i<=n;i++)
            for(R int j=1;j<=n;j++)
                in(res[i][j]);
        long long ans=0;
        int cnt=0;
        for(R int i=1;i<=n;i++)
            for(R int j=i+1;j<=n;j++)
                if(v[i]==1 or v[j]==1)
                    cnt++,ans+=res[i][j];
        printf("%d %lld\n",cnt,ans);
        return 0;
    }
    if(mnn==2)
    {
        for(R int i=1;i<=n;i++)
            for(R int j=1;j<=n;j++)
                in(res[i][j]);
        int tot2=0,pa=-1,pb=-1;
        for(R int i=1;i<=n;i++)
            if(v[i]==2)tot2++;
        long long ans=21474836476666LL;
        if(tot2==2)
        {
            for(R int i=1;i<=n;i++)
            {   
                if(v[i]==2)
                {
                    if(pa==-1)
                        pa=i;
                    else 
                        pb=i;
                }
            }
            ans=res[pa][pb];
            for(R int i=1;i<=n;i++)
                ans+=min(res[i][pa],res[i][pb]);
        }
        for(R int i=1;i<=n;i++)
        {
            long long rs=0;
            for(R int j=1;j<=n;j++)
                rs+=res[i][j];
            ans=min(ans,rs);
        }
        printf("%d %lld",n-1,ans);
    }
    if(mnn==3)mofashi();
    fclose(stdin);
    fclose(stdout);
    return 0;
}

NOIP2018提高組模擬題(五)