AtCoder-arc058(題解)

A - こだわり者いろはちゃん / Iroha's Obsession(暴力)

題目連接c++

題目大意:

給你 \(k\) 個個位數字和一個數字 \(n\) ,要求找到一個大於等於n的數字,使得不出現 \(k\) 個數.數組

大體思路:

直接枚舉就好了,最多枚舉到多一位。session

代碼:

#include<bits/stdc++.h>
using namespace std;
int n,k;
int vis[20];
bool check(int x){
    while(x){
        int d=x%10;
        x/=10;
        if(vis[d])return 0;
    }
    return 1;
}
int main()
{
    //freopen("H:\\c++1\\in.txt","r",stdin);
    //freopen("H:\\c++1\\out.txt","w",stdout);
    scanf("%d%d",&n,&k);
    for(int i=1,x;i<=k;i++)scanf("%d",&x),vis[x]=1;
    for(int i=n;;i++){
        if(check(i)){
            printf("%d\n",i);return 0;
        }
    }
    return 0;
}

B - いろはちゃんとマス目 / Iroha and a Grid(組合數)

題目連接優化

題意大意:

一個 \(H\)\(W\) 的長方形矩陣,在其左下角的 \(X\)\(Y\) 長方形矩陣不能走,問從左上角往下和往右到右下角的方案數 \(()(H,W<=1e5)\)spa

大體思路:

首先要知道,若沒有不能走的格子,從 \((x1,y1)\) 走到 \((x2,y2)\) 的方案數應該是 \(C(x2+y2-x1-y1,x2-x1)\) ,咱們假設左上角座標位 \((1,1)\) ,右下角座標位 \((H,W)\) ,那麼以 \(x=X\) 爲分界線,將可行部分分紅兩個不一樣大小的矩形,那麼對於每個方案必須向右穿過這個分界線,那麼假設 \(y=i\) ,咱們要從 \((X,i) \to (X+1,i)\) ,咱們只要算出從 \((1,1) \to (X,i)\) 的方案數 \(*\)\((X+1,i) \to (H,W)\) 的方案數,而後把分界線上的每個點累加起來就能夠獲得答案,預處理階乘和階乘的逆元。code

代碼:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
const int mod=1e9+7;
ll n,m,a,b;
ll ksm(ll a,ll b){
    ll res=1,t=a;
    while(b){
        if(b&1)res=(res*t)%mod;
        t=(t*t)%mod;
        b>>=1;
    }
    return res;
}
ll jc[N],inv[N];
void init(){
    jc[0]=inv[0]=1;
    for(int i=1;i<N;i++)jc[i]=(jc[i-1]*i)%mod,inv[i]=ksm(jc[i],mod-2);
}
ll C(ll a,ll b){
    return ((jc[b]*inv[a])%mod*inv[b-a])%mod;
}
int main()
{
    //freopen("H:\\c++1\\in.txt","r",stdin);
    //freopen("H:\\c++1\\out.txt","w",stdout);
    init(); // 預處理階乘和逆元
    scanf("%lld%lld%lld%lld",&n,&m,&a,&b);
    ll ans=0;
    for(int i=1;i<=n-a;i++){
        ll ans1=C(b-1,b+i-2); //分界線兩個對應的點
        ll ans2=C(m-b-1,n-i+m-b-1);
        ans=(ans+ans1*ans2)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

C - 和風いろはちゃん / Iroha and Haiku(狀壓DP)

題目大意:

給定 \(,,X,Y,Z\) ,要求生成一個 \(n\) 個數字的序列,每一個數字爲 \(1-10\) ,問全部可能的序列中知足條件:存在 \(1<=x<y<z<w<=n\) ,使得 \(\sum^{x}_{y-1}{a_i}=X,\sum^{y}_{z-1}{a_i}=Y,\sum^{z}_{w-1}{a_i}=Z\) ,的序列個數是多少。 \((X<=5,Y<=7,Z<=5,n<=40)\)隊列

大體思路:

首先對於這種數據範圍很小的題目應該要考慮狀壓dp來解決,首先對於一個數字 \(5\) ,咱們能夠用二進制 \(10000\) 來表示,對於要造成連續的三段和爲 \(,,2,3,1\) 的狀態能夠用二進制 \(10 100 1\) 來表示,將它們拼接起來,那麼對於一個序列 \(,,,2,1,2,1\) 的狀態就能夠用二進制 \(10 1 10 1\) ,能夠發現上面 \(101001\&101101=101001\) 這就表示 \(,,,2,1,2,1\) 序列是知足連續的三段和爲 \(,,2,3,1\) 的。經過這樣構造咱們就能夠進行狀態的轉移了,咱們應該求得是序列不知足條件的個數,最後在減去。字符串

代碼:

#include<bits/stdc++.h>
using namespace std;
const int N=(1<<18);
const int mod=1e9+7;
int dp[50][N];
int n,a,b,c;
int main()
{
    //freopen("H:\\c++1\\in.txt","r",stdin);
    //freopen("H:\\c++1\\out.txt","w",stdout);
    scanf("%d",&n);
    scanf("%d",&a);scanf("%d",&b);scanf("%d",&c);
    int bz=(1<<(a-1))|(1<<(a+b-1))|(1<<(a+b+c-1));//目標狀態
    int mx=(1<<(a+b+c));//總共的狀態數
    dp[0][0]=1;//初始化
    int p=1;
    for(int i=1;i<=n;i++){
        p=(1ll*10*p)%mod;//計算總共的方案數
        for(int j=0;j<mx;j++){//枚舉全部狀態
            if(dp[i-1][j]==0)continue;//上一個此狀態無解
            for(int k=1;k<=10;k++){//枚舉每種方案
                int now=(j<<k)|(1<<(k-1));//更新狀態
                now&=(mx-1);//防止溢出
                if((now&bz)!=bz){//不能構成X,Y,Z
                    dp[i][now]=(dp[i][now]+dp[i-1][j])%mod;
                }
            }
        }   
    }
    int ans=p;
    for(int i=0;i<mx;i++){
        ans=(ans-dp[n][i]+mod)%mod;//
    }
    printf("%d\n",ans);
    return 0;
}

D - 文字列大好きいろはちゃん / Iroha Loves Strings(暴力+dp)

題目大意:

給定 \(N\) 個字符串,選擇一些字符串使得它們的總長爲 \(K\) ,而且它們按照順序拼接起來的字典序最小,保證有解。\((N<=2000,K<=1e4,len_i<=K,\sum{len_i}<=1e6)\)get

大體思路:

首先必然要預處理一個\(dp[i][j]\),表示使用 \(i-n\) 的字符串可否構成 \(j\)長度的字符串,使用 \(bitset\) 優化01揹包string

咱們將可使用的字符串的第 \(1\) 位加入隊列,對於答案的每一位進行構造,對於第 \(i\) 位必然是隊列中的全部字符的最小值,接下來,更新隊列,若是當前位爲該字符串的結尾,那麼表示咱們能夠將其後面的字符串的第一位加入隊列,若是未到結尾就添加該字符串的後一位。

(這個作法不是正解,可是複雜度還行,正解使用了 \(exkmp\) ,有研究研究)

代碼:

//這題很玄,可能會re或者wa
#include<bits/stdc++.h>
using namespace std;
bitset<10010> ok[2010];
pair<int,int> p[2][10010];
char s[2010][10010],ans[1000010];
int len[2010],n,k,cnt=0,ncnt=0;
int main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++) scanf("%s",s[i]+1),len[i]=strlen(s[i]+1);
    ok[n+1][0]=1;
    for (int i=n;i>=1;i--)
        ok[i]=ok[i+1]|(ok[i+1]<<len[i]);//01揹包處理能夠拼成的長度
    for (int i=1;i<=n;i++)
        if (ok[i+1][k-len[i]]) p[0][++cnt]=make_pair(i,1);//加入,使用滾動數組
    int f=0;
    for (int i=1;i<=k;i++)
    {
        char mi='z';
        for (int j=1;j<=cnt;j++) mi=min(mi,s[p[f][j].first][p[f][j].second]);//貪心選擇最小字符
        ans[i]=mi;
        int mx=n+1;//初始化爲大值
        ncnt=0;
        for (int j=1;j<=cnt;j++)
        {
            int id=p[f][j].first,pos=p[f][j].second;
            if (s[id][pos]!=mi) continue;
            if (pos==len[id]) mx=min(mx,id);//當前字符串結束,能夠選擇當前字符串後面的因此字符串
            else p[f^1][++ncnt]=make_pair(id,pos+1);//繼續使用這個字符串
        }
        for (int j=mx+1;j<=n;j++)
            if (k-len[j]-i>=0&&ok[j+1][k-len[j]-i]) p[f^1][++ncnt]=make_pair(j,1);//判斷是否能夠用該字符串
        f^=1;//
        cnt=ncnt;//
    }
    printf("%s\n",ans+1);
}
相關文章
相關標籤/搜索