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;
}
相关文章
相关标签/搜索