AtCoder Grand Contest 030

链接

C. Coloring Torus

构造题。

考虑如果 k500,那么直接构造 111222333 这样的矩阵即可。

但是这种做法不太方便扩展。考虑另一种构造,即构造 1234234134124123 这样的拉丁方。

这样有什么好处,就是奇数行与偶数行被完全分开了,我任意将奇数行的某个数字全部替换成另一个没有出现的数字,得到的矩阵仍然合法。

这样构造的上界是 2n,恰好满足条件。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 3010
#define mod 1000000007
using namespace std;
int ksm(int a,int b=mod-2)
{
    int r=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
    return r;
}
const int iv2=ksm(2);
int f[N][N],a[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) if(a[i]>a[j]) f[i][j]=1;
    for(int t=1;t<=m;t++)
    {
        int x,y;scanf("%d%d",&x,&y);
        f[x][y]=f[y][x]=1ll*(f[x][y]+f[y][x])*iv2%mod;
        for(int i=1;i<=n;i++) if(i!=x && i!=y) f[y][i]=f[x][i]=1ll*(f[x][i]+f[y][i])*iv2%mod,f[i][x]=f[i][y]=1ll*(f[i][x]+f[i][y])*iv2%mod;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++) ans=(ans+f[i][j])%mod;
    printf("%lld ",1ll*ans*ksm(2,m)%mod);
    return 0;
}

D. Inversion Sum

考虑一个很显然的想法是贡献单独计算,即对于每一对 x,y,计算它产生逆序对的贡献。但直接这样计算很麻烦,处理不精细就变成 O(n3) 的。

再考虑另一个转化,即每次交换看作有 12 交换,最后算期望。这样就不用处理是否交换导致的 ×2 问题。

那么考虑一对 x,y 会影响的整数对有哪些,显然只有 fx,i,fi,yO(n) 个。而这样处理后还有一个好处:就是 x,y 之间之前的相对关系无所谓了,即直接有 fx,i=fx,i+fy,i2

总复杂度 O(n2)

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 3010
#define mod 1000000007
using namespace std;
int ksm(int a,int b=mod-2)
{
    int r=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
    return r;
}
const int iv2=ksm(2);
int f[N][N],a[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) if(a[i]>a[j]) f[i][j]=1;
    for(int t=1;t<=m;t++)
    {
        int x,y;scanf("%d%d",&x,&y);
        f[x][y]=f[y][x]=1ll*(f[x][y]+f[y][x])*iv2%mod;
        for(int i=1;i<=n;i++) if(i!=x && i!=y) f[y][i]=f[x][i]=1ll*(f[x][i]+f[y][i])*iv2%mod,f[i][x]=f[i][y]=1ll*(f[i][x]+f[i][y])*iv2%mod;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++) ans=(ans+f[i][j])%mod;
    printf("%lld ",1ll*ans*ksm(2,m)%mod);
    return 0;
}

E. Less than 3

很妙的题。

考虑如果认为 01 之间有一条红线,10 之间有一条蓝线,那么最后序列一定是红蓝线交错。不妨假设开头结尾有无数条红蓝线交错的线,如:

image

显然如果两个式子的红蓝线完全匹配,那么这两个序列是全等的。

由于每次操作不可能增加或减少红蓝线数量,所以每次操作等价于将一条线移动一格。开头操作视为将开头的末尾线段移动到第一格后。并且时刻要求相邻两条线段之间最多差 2 格。

可以发现交叉匹配一定不优,所有排完序后一定是按顺序匹配。答案即距离之和。

image

只需要枚举匹配第一个线段的线段是哪一条即可。复杂度 O(n2)

注意要保证匹配线段的颜色相同。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 5010
using namespace std;
char s[N],t[N];
int x[N],xt,y[N],yt,n;
int get(int a[],int at,int i){return i<0?0:i>at?n:a[i];}
int main()
{
    scanf("%d",&n);
    scanf("%s%s",s+1,t+1);
    for(int i=1;i<n;i++){if(s[i]!=s[i+1]) x[++xt]=i;if(t[i]!=t[i+1]) y[++yt]=i;}
    int ans=n*n,m=xt+yt;
    for(int i=(xt&1)^(s[1]!=t[1]);i<=m+1;i+=2)
    {
        int res=0;
        for(int j=-m;j<=m;j++) res+=abs(get(x,xt,j)-get(y,yt,i-xt+j));
        ans=min(ans,res);
    }
    printf("%d\n",ans);
    return 0;
}

F. Permutation and Minimum

考虑分类,显然只要将 (a,b),(1,1),(a,1) 三组分开,最后乘上 (1,1) 组的阶乘,剩下的部分就与相对顺序无关了。

考虑从大到小枚举数字,这样的好处是:如果它匹配了一个填了一半的组,那么这个组的最小值就是它,否则如果将一个空组填了一般,那么最小值一定不是它。

fi,j,k 表示当前要填 i,当前有 j 个填了一半的 (1,1) 组,有 k 个已经填了的 (a,1) 组。

如果 i 属于 (a,b) 组,那么什么都不干。

如果 i 属于 (1,1) 组,那么可以新开一个 fi1,j+1,k,可以填一个 (1,1)fi1,j1,k,可以填一个 (a,1)×kfi1,j,k1

如果 i 属于 (a,1) 组,那么可以新开一个 fi1,j,k+1,可以填一个 (1,1)fi1,j1,k

复杂度 O(n3)

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 310
#define mod 1000000007
using namespace std;
int f[2][N][N],a[N*2];//f[i][j][k]:填到 i,有 j 对填了一半的 -1,-1,k 对未匹配的 a,-1
short vis[N*2];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=2*n;i++) scanf("%d",&a[i]);
    int tt=0;
    for(int i=1;i<=2*n;i+=2)
        if(~a[i] && ~a[i+1]) vis[a[i]]=vis[a[i+1]]=2;
        else if(~a[i] || ~a[i+1]) vis[a[i]+a[i+1]+1]=1;
        else ++tt;
    f[0][1][1]=1;
    for(int i=2*n;i;i--)if(vis[i]!=2)
    {
        swap(f[0],f[1]),memset(f[0],0,sizeof(f[0]));
        auto g=f[0];
        for(int j=1;j<=n+1;j++)
            for(int k=1;j+k<=n+2;k++)
            if(f[1][j][k])
            {
                int v=f[1][j][k];
                if(vis[i]==1) g[j-1][k]=(g[j-1][k]+v)%mod,g[j][k+1]=(g[j][k+1]+v)%mod;
                else g[j-1][k]=(g[j-1][k]+v)%mod,g[j][k-1]=(g[j][k-1]+1ll*v*(k-1))%mod,g[j+1][k]=(g[j+1][k]+v)%mod;
            }
    }
    int res=f[0][1][1];
    for(int i=1;i<=tt;i++) res=1ll*res*i%mod;
    printf("%d",res);
    return 0;
}
posted @   Flying2018  阅读(7)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示