湖南省第十五届程序设计竞赛解题报告

A 全 1 子矩阵
签到

#include
using namespace std;
const int N=15;
int n,m;
char s[N][N],t[N][N];
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%s",s[i]+1);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            t[i][j]=s[i][j];
        for(int i=1;i<=n;i++)
            for(int j=2;j<=m;j++)
            if(s[i][j]=='1'&&s[i][j-1]=='1') s[i][j-1]='0';
        for(int j=1;j<=m;j++)
            for(int i=2;i<=n;i++)
            if(s[i][j]=='1'&&s[i-1][j]=='1') s[i-1][j]='0';
        for(int i=1;i<=n;i++)
            for(int j=m-1;j>=1;j--)
            if(t[i][j]=='1'&&t[i][j+1]=='1') t[i][j+1]='0';
        for(int j=1;j<=m;j++)
            for(int i=n-1;i>=1;i--)
            if(t[i][j]=='1'&&t[i+1][j]=='1') t[i+1][j]='0';
        int ans=0,res=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
        {
            if(s[i][j]=='1') ans++;
            if(t[i][j]=='1') res++;
        }
        printf(ans==1&&res==1?"Yes\n":"No\n");
    }
}

B 组合数
使得k=min(k,n-k),那么k就小于等于n/2了,然后暴力去计算组合数,组合数就会只增大不减小,大于1e18直接输出1e18。

#include
using namespace std;
typedef __int128 ll;
void print(ll x)
{
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
int main()
{
    ll n,k;
    long long x,y;
    while(scanf("%lld%lld",&x,&y)!=EOF)
    {
        y=min(y,x-y);
        n=x;k=y;
        ll ans=1;
        for(ll i=1;i<=k;i++)
        {
            ans=ans*n/i;
            n--;
            if(ans>1e18) break;
        }
        if(ans>1e18) ans=1e18;
        print(ans);putchar('\n');
    }
}

C Distinct Substrings
对于一个长度为n的数组a1,a2,a3…an,每次只会更新an,an-1 an,an-2 an-1 an,a1… an-1 an,这n个后缀或者新加一个字符

所以最多只会增加n+1个后缀,考虑哪些字符新加某个字符已经出现过了

就是检查每一个在它之前的后缀,看看它之前的后缀是否包含它了

对于第i个后缀和第j个后缀(i

这不就是kmp的性质吗,所以我们倒过来求kmp即可,kmp求出的是以i结尾的后缀与字符串的前缀的最大长度。

而对于单个字符的,加个vis数组判断单个字符是否出现过。

#include
using namespace std;
typedef long long ll;
const int N=1e6+5,mod=1e9+7;
int n,top,m,a[N],b[N],nex[N],vis[N];
pairs[N];
void kmpinit()
{
    int i=1,k=0;
    while(i<=n)
        if(k==0||a[i]==a[k]) nex[++i]=++k;
    else k=nex[k];
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        top=0;
        for(int i=1;i<=m;i++) vis[i]=false;
        for(int i=1;i<=n;i++) nex[i]=0;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]),vis[a[i]]=true;
        reverse(a+1,a+1+n);
        kmpinit();
        for(int i=1;i<=n;i++)
        {
            int now=nex[i+1]-1;
            if(now&&i>now) s[++top]={a[i-now],now};
        }
        sort(s+1,s+1+top);
        top=unique(s+1,s+1+top)-s-1;
        for(int i=1;i<=top;i++) vis[s[i].first]++;
        ll ans=0,res=1;
        for(int i=1;i<=m;i++)
        {
            res=res*3%mod;
            ans^=res*(n+1-vis[i])%mod;
        }
        printf("%lld\n",ans);
    }
}

D Modulo Nine
区间乘能取模9等于0,就是要包含两个3的因子,其中0,9包含两个,3,6包含一个,其它的包含0个,对于多个l1,r,l2,r,r相同只需要最小的区间满足了,那么大的区间也同样满足了,暴力O(n^3)dp转移。

#include
using namespace std;
typedef long long ll;
const int N=55,mod=1e9+7;
int n,m,f[N];
ll dp[N][N][N];
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(f,0,sizeof(f));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=m;i++)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            f[r]=max(f[r],l);
        }
        dp[0][0][0]=1;
        for(int i=0;i;i++)
            for(int j=0;j<=i;j++)
            for(int k=0;k<=j;k++)
            if(f[i]<=k)
        {
            dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k]*6)%mod;
            dp[i+1][i+1][j]=(dp[i+1][i+1][j]+dp[i][j][k]*2)%mod;
            dp[i+1][i+1][i+1]=(dp[i+1][i+1][i+1]+dp[i][j][k]*2)%mod;
        }
        ll ans=0;
        for(int j=0;j<=n;j++)
            for(int k=f[n];k<=j;k++)
            ans=(ans+dp[n][j][k])%mod;
        printf("%lld\n",ans);
    }
}

E Numbers
算一算时间复杂度,就可以爆搜了。

#include
using namespace std;
const int N=55;
char s[N];
int n,ans;
bool vis[N<<1];
void dfs(int pos)
{
    if(pos==n) {ans++;return;}
    if(s[pos+1]=='0')
    {
        if(!vis[0])
        {
            vis[0]=true;
            dfs(pos+1);
            vis[0]=false;
        }
    }
    else
    {
        int x=s[pos+1]-'0';
        if(!vis[x])
        {
            vis[x]=true;
            dfs(pos+1);
            vis[x]=false;
        }
        x=x*10+s[pos+2]-'0';
        if(!vis[x]&&pos+2<=n)
        {
            vis[x]=true;
            dfs(pos+2);
            vis[x]=false;
        }
    }
}
int main()
{
    while(scanf("%s",s+1)!=EOF)
    {
        n=strlen(s+1);ans=0;
        dfs(0);
        printf("%d\n",ans);
    }
}

F 4 Buttons

#include
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int main()
{
    ll n,a,b,c,d;
    while(scanf("%lld%lld%lld%lld%lld",&n,&a,&b,&c,&d)!=EOF)
    {
        swap(b,c);
        ll ans=n*(n-1)/2%mod*(c+d)%mod*(a+b)%mod+c*n+d*n+1+a*n+b*n;
        printf("%lld\n",ans%mod);
    }
}

G 字典序
暴力贪心求解,满足条件的区间为不增的区间,初始要找的满足条件的区间是[1,n],枚举列找到第一个满足条件的区间,然后把一系列连续的相等的区间找出来构成新区间,再贪心枚举其它的列。现在赛csu可用的内存大,直接放了O(1)的st表,牛客上只能放个O(log(2000))的线段树,但其实没必要,一个更简单时效更低的方法是每个格子记录它上面第一个不符合条件的位置的距离就可以了。

#include
using namespace std;
const int N=2e3+5;
int n,m,a[N][N];
bool t[N][N*3],b[N][N],vis[N];
void build(int l,int r,int k,int now)
{
    if(l==r)
    {
        t[now][k]=b[now][l];
        return;
    }
    int m=l+r>>1;
    build(l,m,k<<1,now);build(m+1,r,k<<1|1,now);
    t[now][k]=t[now][k<<1]|t[now][k<<1|1];
}
void solve()
{
    for(int j=1;j<=m;j++)
    {
        for(int i=2;i<=n;i++)
            if(a[i-1][j]>a[i][j]) b[j][i]=true;
            else b[j][i]=false;
        build(1,n,1,j);
    }
}
int query(int l,int r,int k,int x,int y,int now)
{
    if(ry) return 0;
    if(l>=x&&r<=y) return t[now][k];
    int m=l+r>>1;
    return query(l,m,k<<1,x,y,now)|query(m+1,r,k<<1|1,x,y,now);
}
int query(int j,int l,int r)
{
    assert(l<=r);
    return query(1,n,1,l,r,j);
}
int top,tot;
pairv[N],res[N];
bool judge(int x)
{
    assert(tot<=2000);
    for(int i=1;i<=tot;i++)
        if(query(x,v[i].first+1,v[i].second)) return false;
    top=0;
    for(int i=1;i<=tot;i++)
    {
        int l=v[i].first,r=v[i].second,t=l;
        for(int j=l+1;j<=r;j++)
            if(a[j][x]!=a[j-1][x])
        {
            if(j-t>=2) res[++top]={t,j-1};t=j;
        }
        if(r-t+1>=2) res[++top]={t,r};
        assert(top<=2000);
    }
    for(int i=1;i<=top;i++) v[i]=res[i];
    tot=top;
    return true;
}
int ans[N];
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(vis,false,sizeof(vis));
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
        solve();
        tot=0;
        if(n>1) v[++tot]={1,n};
        memset(ans,0,sizeof(ans));
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=m;j++)
                if(!vis[j]&&judge(j))
            {
                vis[j]=true;ans[i]=j;break;
            }
            if(!ans[i]) break;
        }
        if(!ans[m]) printf("-1\n");
        else for(int i=1;i<=m;i++)
            printf(i==m?"%d\n":"%d ",ans[i]);
    }
}

H 有向图
线性代数题,附:https://blog.csdn.net/qq_43202683/article/details/100170570#comments

#include
using namespace std;
typedef long long ll;
const int N=550,mod=1e9+7;
int n,m;
ll inv10000,a[N][N],p[N][N];
ll inv(ll x){return x==1?1:(mod-mod/x)*inv(mod%x)%mod;}
void inv_mt(ll a[N][N],int n)
{
    for(int i=1;i<=n;i++)
    {
        int p=i;
        while(p) p++;
        swap(a[p],a[i]);
        ll t=inv(a[i][i]);
        for(int j=i;j<=n+n;j++) a[i][j]=a[i][j]*t%mod;
        for(int k=1;k<=n;k++)
        {
            if(k==i) continue;
            t=a[k][i];
            for(int j=i;j<=n+n;j++)
                a[k][j]=(a[k][j]-t*a[i][j]%mod+mod)%mod;
        }
    }
}
int main()
{
    inv10000=inv(10000);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n+m;j++)
            scanf("%lld",&p[i][j]),p[i][j]=p[i][j]*inv10000%mod;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n+n;j++)
        {
            if(j<=n)
                a[i][j]=((i==j)-p[i][j]+mod)%mod;
            else
                a[i][j]=j-n==i;
        }
        inv_mt(a,n);
        for(int i=1;i<=m;i++)
        {
            ll ans=0;
            for(int j=1;j<=n;j++)
            {
                ans=(ans+a[1][n+j]*p[j][n+i]%mod)%mod;
            }
            printf(i==m?"%lld\n":"%lld ",ans);
        }
    }
}

I 2019
树型dp

#include
using namespace std;
const int N=2e4+5,mod=2019;
int n,ans,tot,head[N],dp[N][mod],nex[N<<1],to[N<<1],wi[N<<1];
void add(int u,int v,int w){to[++tot]=v;nex[tot]=head[u];head[u]=tot;wi[tot]=w;}
void dfs(int u,int p)
{
    memset(dp[u],0,sizeof(dp[u]));
    for(int i=head[u];i;i=nex[i])
    {
        int v=to[i];if(v==p) continue;
        dfs(v,u);
        ans+=dp[u][(mod-wi[i])%mod];
        for(int j=0;j;j++)
            ans+=dp[v][j]*dp[u][(mod-j-wi[i]+mod)%mod];
        dp[u][wi[i]]++;
        for(int j=0;j;j++)
            dp[u][(j+wi[i])%mod]+=dp[v][j];
    }
    ans+=dp[u][0];
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        tot=ans=0;for(int i=1;i<=n;i++) head[i]=0;
        for(int i=1;i;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
        }
        dfs(1,0);
        printf("%d\n",ans);
    }
}

J Parity of Tuples (Easy)
要解本题必须要注意一句至关重要的话:在这里插入图片描述
^运算是按位与。。。
没看到这句话,那么案例都推不出。

#include
using namespace std;
typedef long long ll;
const int N=1e4+5,mod=1e9+7;
int n,m,k,a[10];
ll bit[35],dp[35][1<<10];
ll solve()
{
    for(int i=0;i<=k;i++)
        for(int j=0;j<1<;j++) dp[i][j]=0;
    for(int i=0;i;i++) scanf("%d",&a[i]);
    dp[0][0]=1;
    for(int i=0;i;i++)
    {
        int st=0;
        for(int j=0;j;j++) st|=(a[j]>>i&1)<;
        for(int j=0;j<1<;j++)
        {
            dp[i+1][j]=(dp[i+1][j]+dp[i][j])%mod;
            dp[i+1][j^st]=(dp[i+1][j^st]+bit[i]*dp[i][j])%mod;
        }
    }
    return dp[k][(1<)-1];
}
int main()
{
    bit[0]=3;
    for(int i=1;i<35;i++) bit[i]=bit[i-1]*bit[i-1]%mod;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    {
        int ans=0;
        for(int i=1;i<=n;i++)
            ans=(ans+solve())%mod;
        printf("%d\n",ans);
    }
}

K 双向链表练习题
翻转链表时,链表之间的边是不变化的,对每个链表给出一个指针l,r记录头和尾,翻转就是交换头和为,如果两个链表相连,将两个链表的头和尾连一条边,记录链表的状态是不是为空,最后一遍dfs过。也可以用平衡树写。

#include
using namespace std;
const int N=1e5+5;
int n,m,l[N],r[N],tot,ep[N],head[N],nex[N<<1],to[N<<1];
void add(int u,int v){to[++tot]=v;nex[tot]=head[u];head[u]=tot;}
int top,s[N];
bool vis[N];
void dfs(int u)
{
    s[++top]=u;vis[u]=true;
    for(int i=head[u];i;i=nex[i])
    {
        int v=to[i];
        if(vis[v]) continue;
        dfs(v);
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++) l[i]=r[i]=i,ep[i]=vis[i]=false,head[i]=0;
        tot=0;
        for(int i=1;i<=m;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            if(!ep[a]&&!ep[b])
            {
                add(r[a],l[b]);
                add(l[b],r[a]);
                r[a]=r[b];
                swap(l[a],r[a]);
                ep[b]=true;
            }
            else if(ep[a]&&!ep[b])
            {
                l[a]=l[b];r[a]=r[b];
                swap(l[a],r[a]);
                ep[a]=false;ep[b]=true;
            }
            else if(ep[b]&&!ep[a])
            {
                swap(l[a],r[a]);
            }
        }
        if(ep[1]) printf("0\n");
        else
        {
            top=0;
            dfs(l[1]);
            printf("%d ",top);
            for(int i=1;i<=top;i++)
                printf(i==top?"%d\n":"%d ",s[i]);
        }
    }
}

你可能感兴趣的:(湖南省第十五届程序设计竞赛解题报告)