AtCoder Grand Contest 016

AtCoder Grand Contest 016

A - Shrinking

你能夠進行一個串的變換,把一個長度爲\(n\)的串\(S\)能夠變成長度爲\(n-1\)的串\(T\),其中\(T_i\)要麼是\(S_i\)要麼是\(S_{i+1}\)ios

如今問你最少進行多少次這個操做,可以使最終獲得的\(T\)只由一個字符構成。spa

\(|S|\le 100\)code

首先枚舉最終字符是哪個。那麼首先在\(S\)末尾加上一個這個字符,那麼這個最小步數等於對於全部位置而言,離它最近的枚舉的字符到這個位置的距離。遞歸

那麼直接模擬就好了,複雜度\(O(n\sum)\)遊戲

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char ch[111];
int a[111],n,ans=2e9;
int main()
{
    scanf("%s",ch+1);n=strlen(ch+1);
    for(int i=1;i<=n;++i)a[i]=ch[i]-97;
    for(int i=0;i<26;++i)
    {
        int mx=0,d=0;
        for(int j=n;j;--j)
            if(a[j]==i)d=0;
            else mx=max(mx,++d);
        ans=min(ans,mx);
    }
    printf("%d\n",ans);
    return 0;
}

B - Colorful Hats

\(n\)我的,每一個人有一頂帽子,如今每一個人會告訴你除本身外的全部人的帽子一共有多少種顏色。get

你要判斷是否存在一個合併方案知足全部人的陳述。string

\(n\le 10^5\)it

首先不難發現最大值和最小值的差最大是\(1\)。那麼咱們能夠獲得最大值的顏色一定出現了屢次,最小值的帽子一定只出現了一次。io

那麼直接大力討論一下就行了。class

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,a[100100];
void WA(){puts("No");exit(0);}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)a[i]=read();
    sort(&a[1],&a[n+1]);
    if(abs(a[n]-a[1])>1){puts("No");return 0;}
    if(a[n]==a[1])
    {
        if(a[n]==1||a[n]==n-1||2*a[n]<=n)puts("Yes");
        else puts("No");
        return 0;
    }
    int cnt=0;
    for(int i=n;i;--i)if(a[i]==a[n])++cnt;
    int v=a[n]-(n-cnt);
    if(v>0&&2*v<=cnt&&a[1]==v+(n-cnt)-1)puts("Yes");
    else puts("No");
    return 0;
}

C - +/- Rectangle

你須要構造一個\(H\times W\)的矩陣,每一個值都是\([-10^9,10^9]\)之間,要求矩陣的全部元素和是正數,且每個\(h\times w\)的子矩陣的和都是負數。

\(H,W,h,w\le 500\)

一個想法是首先把全部位置所有填滿,而後把全部\(h\times w\) 的右下角給填上一個負數知足包含這個位置的子矩形都變成負數。那麼就這樣子構造一下而後\(check\)一下是否合法。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 555
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int H,W,h,w;
int a[MAX][MAX];
long long sum=0;
int main()
{
    H=read();W=read();h=read();w=read();
    for(int i=1;i<=H;++i)
        for(int j=1;j<=W;++j)
            a[i][j]=1000;
    for(int i=h;i<=H;i+=h)
        for(int j=w;j<=W;j+=w)
            a[i][j]=-h*w*1000+999;
    for(int i=1;i<=H;++i)
        for(int j=1;j<=W;++j)sum+=a[i][j];
    if(sum<0)puts("No");
    else
    {
        puts("Yes");
        for(int i=1;i<=H;++i,puts(""))
            for(int j=1;j<=W;++j)
                printf("%d ",a[i][j]);
    }
    return 0;
}

D - XOR Replace

給你一個數列\(a_i\),每次你能夠把\(a\) 中的一個數替換爲\(a\)中全部數的異或和。

問可否把\(a\)變成給定的\(b\)。若是能,給出最小的步驟。

\(n\le 10^5,a_i\le 2^{30}\)

首先手玩一下,能夠把這個步驟理解爲:額外補充一個\(a_{n+1}\)位置,爲前面全部數的異或和,每次操做等價於把\(i\in [1,n]\)的一個數和\(a_{n+1}\)進行交換。

那麼這樣子就能夠很容易的把\(-1\)給判掉。

對於剩下的部分,考慮每一對一一對應的位置,若是\(a_i=b_i\),那麼顯然不用管了。不然的話從\(a_i\)\(b_i\)連一條邊,那麼這一條邊至少要貢獻一次操做。這樣子會把若干個表明值域的點連起來。首先先考慮一下特殊點的狀況,假如每一個元素都只出現了一次,那麼這樣子就會造成一堆鏈,顯然從鏈首到鏈尾一路走過去就好了,跨越鏈的時候須要額外進行一次交換操做,因此答案還須要加上聯通塊個數-1。顯然對於權值屢次出現的狀況聯通塊的問題也是同樣的。

因此答案就是邊數加上聯通塊個數減一。

注意這樣一個問題,由於最後一個元素,即初始的異或和咱們沒有連邊出去,那麼此時等於須要先進行一次交換到達某個聯通塊才能繼續操做,因此若是有這樣子的狀況的話答案要額外加一。

#include<iostream>
#include<cstdio>
#include<set>
#include<map>
using namespace std;
#define MAX 100100
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
map<int,int> M;
multiset<int> S;
int f[MAX],tot;
int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
int ID(int x){return M[x]?M[x]:M[x]=++tot;}
int n,a[MAX],b[MAX],ans=0;
int main()
{
    n=read();
    for(int i=1;i<=n;++i)a[i]=read(),S.insert(a[i]),a[n+1]^=a[i];
    for(int i=1;i<=n;++i)b[i]=read(),b[n+1]^=b[i];
    S.insert(a[n+1]);
    for(int i=1;i<=n;++i)
        if(S.find(b[i])==S.end()){puts("-1");return 0;}
        else S.erase(S.find(b[i]));
    for(int i=1;i<=n+1;++i)f[i]=i;
    for(int i=1;i<=n;++i)
        if(a[i]!=b[i])
        {
            int u=ID(a[i]),v=ID(b[i]);
            f[getf(u)]=getf(v);++ans;
        }
    if(!ans){puts("0");return 0;}
    for(int i=1;i<=tot;++i)if(getf(i)==i)++ans;
    bool fl=true;
    for(int i=1;i<=n;++i)if(b[i]==a[n+1])fl=false;
    ans+=fl;ans-=1;
    printf("%d\n",ans);
    return 0;
}

E - Poor Turkeys

\(n\)只火雞被擺成了一排。依次來了\(m\)我的。

每一個人會進行以下操做:

若是火雞\(x_i\)和火雞\(y_i\)都還活着,那麼就等機率的吃掉其中一隻。

若是隻剩下一隻就吃掉那一隻。

若是都死了就啥都不幹。

問有多少對雞\((i,j)\)知足\(m\)我的都操做完了以後,這兩隻雞都還可能活着。

\(n\le 400,m\le 10^5\)

考慮一個枚舉任意一對以後怎麼計算,那麼顯然只要存在一我的要吃這兩隻雞中的任何一隻,那麼就直接欽定吃掉另一隻,若是不行的話那麼這一對確定不合法。

可是這樣子的複雜度是\(O(n^2m)\) 的。咱們須要尋求更加優秀的方法。

一個不難想到的想法是對於每隻雞維護一個集合,表示若是這隻雞最後想要活下來,那麼哪些雞必須死。若是咱們可以求出這個東西的話,咱們只須要判斷兩個點的集合是否有交就好了。

考慮這個東西怎麼求,若是咱們按照順序進行的話,由於咱們只欽定了這一隻雞不被吃掉,因此與這隻雞無關的雞咱們都不知道會發生什麼,那麼對於兩個集合判交的時候顯然不具有有正確性。那麼咱們時間倒流,既然這隻雞必須存活,那麼此時咱們就能夠知道在進行此次操做以前某隻雞是否必須存活,這樣子咱們就能夠獲得一個若是這隻雞最後或者,哪些雞必須不能死。

接下來再口胡一下爲何若是兩個集合有交就不合法。若是兩隻不一樣的雞不想死,那麼在這二者的集合中有一隻相同的雞不能死(注意一下這個所謂的死是指在某次操做之前不能死,也就是這隻雞爲會了救某隻特定的雞而死,那麼在救到這隻特定的雞以前這隻雞就不能死)。這裏討論一下,若是這隻雞在兩個集合中救的是同一只雞,那麼遞歸處理。不然就的雞是不一樣的,由於這隻雞隻能死一次,因此它只能救下一隻雞,因此一定有一隻雞救不活,致使目標雞也救不活。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 100100
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int ans,n,m,a[MAX],b[MAX];
bool alv[404][404],book[MAX];
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;++i)a[i]=read(),b[i]=read();
    for(int i=1;i<=n;++i)
    {
        alv[i][i]=true;
        for(int j=m;j;--j)
        {
            int u=a[j],v=b[j];
            if(alv[i][u]&&alv[i][v]){book[i]=true;break;}
            if(alv[i][u]||alv[i][v])alv[i][u]=alv[i][v]=true;
        }
    }
    for(int i=1;i<=n;++i)
        for(int j=i+1;j<=n;++j)
        {
            if(book[i]||book[j])continue;
            bool fl=false;
            for(int k=1;k<=n;++k)if(alv[i][k]&&alv[j][k]){fl=true;break;}
            if(!fl)++ans;
        }
    printf("%d\n",ans);
    return 0;
}

F - Games on DAG

給你一個\(n\)個點\(m\)條邊的\(DAG\),問你這個\(DAG\)的全部\(2^m\)個生成子圖中,兩我的在上面玩遊戲,初始時在\(1,2\)兩個點放上一個棋子,而後把一個棋子沿着一條邊移動,不能操做者輸。

問先手勝的子圖個數。

\(n\le 15\)

顯然要考慮的是\(SG\)值那套理論,先手必勝就是兩個點\(SG\)值異或和不爲\(0\)。這個東西顯然很差算,那麼就容斥一下,改爲要算\(1,2\)兩個點的異或和必須爲\(0\)

邊數能夠到\(O(n^2)\)級別,因此顯然不能對於邊進行狀壓。那麼考慮對於點進行狀壓。

\(f[S]\)表示考慮點集\(S\)之間的連邊的時候\(SG(1)=SG(2)\)的方案數。

考慮怎麼進行轉移,那麼咱們顯然是要考慮兩個集合而後將他們合併。

假設兩個集合分別是\(S,T\)。顯然直接枚舉兩個集合咱們是無法作的。

不妨令必敗點集合爲\(S\),那麼其補集\(T\)就是必勝點集合。

考慮\(S,T\)以內的連邊狀況,首先\(S\)內部不能有邊(顯然必敗點之間不相鄰),而後必勝點一定存在一個後繼是必敗點,因此\(T\)中每一個點至少有一條邊連向\(S\)。而\(S\)\(T\)連邊是隨意的。

接下來考慮\(T\)內部的連邊方案數,雖然在枚舉的時候咱們欽定了\(T\)是必勝點集合,可是單獨把\(T\)拿出來看\(T\)可能存在一些點\(SG\)值爲\(0\),因而咱們新構一個虛擬點,讓全部\(T\)中的點都連向這個虛擬點,這樣子全部點的\(SG\)值都增長了\(1\),也就所有變成了必勝點。而在前面的連邊過程當中,咱們\(T\)中任意一個點都連向了一個\(SG\)值等於零的必敗點集合\(S\),而必敗點的\(SG\)值剛好爲\(0\),所以\(T\)內部連邊且知足\(SG(1)=SG(2)\)的方案數就是\(f[T]\)

同時注意由於\(SG(1)=SG(2)\),因此點集中必須\(1,2\)同時出現,不然就是一個不合法的狀態。

#include<iostream>
#include<cstdio>
using namespace std;
#define MOD 1000000007
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,m,G[15],bul[1<<15],bin[25],f[1<<15];
int main()
{
    n=read();m=read();
    for(int i=1,u,v;i<=m;++i)u=read()-1,v=read()-1,G[u]|=1<<v;
    int N=(1<<n)-1;f[0]=1;
    for(int i=1;i<=N;++i)bul[i]=bul[i>>1]+(i&1);
    bin[0]=1;for(int i=1;i<=n;++i)bin[i]=(bin[i-1]<<1)%MOD;
    for(int i=2;i<=N;++i)
        if((i&1)==((i>>1)&1))
            for(int U=i;U;U=(U-1)&i)
                if((U&1)==((U>>1)&1))
                {
                    int T=i^U,w=1;
                    for(int j=0;j<n;++j)
                        if(i&(1<<j))
                        {
                            if(U&(1<<j))w=1ll*w*bin[bul[G[j]&T]]%MOD;
                            else w=1ll*w*(bin[bul[G[j]&U]]-1)%MOD;
                        }
                    f[i]=(f[i]+1ll*f[T]*w)%MOD;
                }
    int ans=1;for(int i=1;i<=m;++i)ans=(ans<<1)%MOD;
    ans=(ans+MOD-f[N])%MOD;
    printf("%d\n",ans);
    return 0;
}
相關文章
相關標籤/搜索