CODE FESTIVAL 2016 Final

链接

D. Pair Cards

对于 i[0,m) 统计有多少对相同的数 xmodm=i,以及有多少数 xmodm=i。考虑贪心匹配,首先 0m2 直接自己匹配,对于 i[1,m2),将它与 mi 匹配,优先将散的匹配对面的,然后再用一对的匹配,如果匹配光了就自己匹配。

正确性显然。复杂度 O(n+m)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
using namespace std;
int a[N],b[N],c[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int mx=0;
    for(int i=1,x;i<=n;i++) scanf("%d",&x),c[x]++,mx=max(mx,x);
    for(int i=1;i<=mx;i++) a[i%m]+=c[i]&1,b[i%m]+=c[i]/2;
    int res=a[0]/2+b[0];
    if(m%2==0) res+=a[m/2]/2+b[m/2];
    for(int i=1;i*2<m;i++)
    {
        int v=min(a[i],a[m-i]);
        res+=v;a[i]-=v,a[m-i]-=v;
        if(a[i]){v=min(a[i]/2,b[m-i]);res+=v*2;a[i]-=v*2,b[m-i]-=v;}
        if(a[m-i]){v=min(a[m-i]/2,b[i]);res+=v*2;a[m-i]-=v*2,b[i]-=v;}
        res+=b[i]+b[m-i];
    }
    printf("%d\n",res);
    return 0;
}

E. Cookies

假设总共吃了 k 次(假设最后剩下的饼干也被吃掉,此时 X 就是剩下的饼干数量),其中第 i 次吃之前额外做了 ti 秒的饼干。发现第 1 次吃时有 X=t1,第二次吃时有 X=t1×t2,第三次吃时有 X=t1t2×t3。所以有 X=ti。总时间是 ti+(k1)a

考虑每次吃 X 至少翻一倍,所以 k 不会超过 60。考虑枚举 k,令 C=(k1)a,根据基本不等式有:

i[1,k]tiktikknk

等号成立条件是 ti 全部相等且 ti=n

显然当 tiZ+ 时,ti{nk,nk} 时最优。枚举有多少个选 nk 即可。

复杂度 O(log2n)O(log3n)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 100010
#define ll long long
using namespace std;
int main()
{
    ll n,a;scanf("%lld%lld",&n,&a);
    ll ans=1e18;
    for(int i=1;i<=60;i++)
    {
        ll x=floor(pow(n,1.0/i));
        for(int j=0;j<=i;j++)
        {
            ll s=1;
            for(int k=1;k<=i-j;k++) s*=x;
            for(int k=i-j+1;k<=i;k++) s*=(x+1);
            if(s>=n){ans=min(ans,x*(i-j)+(x+1)*j+(i-1)*a); break;}
        }
    }
    printf("%lld\n",ans);
    return 0;
}

F. Road of the King

很妙的题。

首先因为这条路径是从 1 出发的,所以 1 必然能到路径经过的所有点。所以只需要所有点能到 1 即强连通。

容易发现如果一个点和 1 强连通,那么这个点与 1 号点可以认为是等价的。所以我们只需要知道现在是否在强连通分量里即可。

同样,一个不包含 1 的子强连通分量也不必记录,因为路径中前者一定能到达后者,而如果存在某一刻连到 1 所在的连通分量中,那么所有路径上的点都并入 1 所在的强连通分量。

考虑 dp。用 fi,j,k 表示当前路径长度为 i,强连通分量大小为 j,被访问过但是没有在连通分量的点数为 k 的方案数。

转移考虑枚举下一个点是哪一类点。复杂度 O(n3)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 310
#define mod 1000000007
using namespace std;
int f[N][N][N];
void add(int &u,int v){u=(u+v>=mod?u+v-mod:u+v);}
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    f[0][1][0]=1;
    for(int i=0;i<m;i++)
        for(int j=1;j<=i+1;j++)
            for(int k=0;j+k<=i+1;k++) if(f[i][j][k])
            {
                add(f[i+1][j][k+1],1ll*f[i][j][k]*(n-j-k)%mod);
                add(f[i+1][j][k],1ll*f[i][j][k]*k%mod);
                add(f[i+1][j+k][0],1ll*f[i][j][k]*j%mod);
            }
    printf("%d\n",f[m][n][0]);
    return 0;
}

G. Zigzag MST

观察 kruskal,可以发现 z 边一定会在 z+1 边加入前被尝试加入。换句话说在 z+1 边被加入时,z 边两端一定是连通的。

所以原题的连边方式可以看成:(a,b) 连边权为 c(a+i1,a+i) 连边权为 c+2i1(b+i1,b+i) 连边权为 c+2i

这样除了 (a,b) 边,剩下的都是在 (i,i+1) 之间的边,对两端相同的边取最小值加入生成树即可。

复杂度 O(mlogm)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 400010
using namespace std;
int g[N],f[N];
struct road{
    int x,y,w;
}r[N];
int find(int x){return f[x]==x?f[x]:(f[x]=find(f[x]));}
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof(g));
    for(int i=0;i<n;i++) f[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x,y,w;scanf("%d%d%d",&x,&y,&w);
        r[i]=(road){x,y,w};
        g[x]=min(g[x],w+1);
        g[y]=min(g[y],w+2);
    }
    for(int i=0;i<n*2;i++) g[(i+1)%n]=min(g[(i+1)%n],g[i%n]+2);
    for(int i=0;i<n;i++) r[++m]=(road){i,(i+1)%n,g[i]};
    sort(r+1,r+m+1,[&](road a,road b){return a.w<b.w;});
    long long ans=0;
    for(int i=1;i<=m;i++)
    {
        int x=find(r[i].x),y=find(r[i].y);
        if(x==y) continue;
        f[x]=y;
        ans+=r[i].w;
    }
    printf("%lld\n",ans);
    return 0;
}

H. Tokaido

首先对于 M=1 的部分。注意到因为所有格子权值都是非负数,所以如果当前行动的人棋子所在格子的下一格不是对手的棋子,它一定会往下一格走。

考虑 dp,用 fi 表示当前对手在 i ,自己在 i1 时自己最大分数减对手最大分数。用 si 表示 jiaj,有 fi=maxj>i(ajsj1fj)+si

max 内部分与 i 无关,直接维护当前最大值即可。复杂度 O(n)

对于 M>1。不妨换种角度考虑:对手棋子在 i,自己棋子在 i+1。如果这一步移到 i+1,相当于取 ai+1 同时互换先后手。否则等于放弃 ai+1 同时保留先手权。所以 dp 方程为:fi=|ai+1fi+1|

考虑维护 F(x) 表示当最后一个数字为 x 时的值。显然 F(x) 图像始终是一条折线。f3(x)=F(x),fn(x)=x,然后递推式与 dp 方程类似。

fi+1(x)fi(x) 不是很好维护。考虑倒过来,即维护 gi(x),使得 gi(fi(x))=F(x)。显然 g3(x)=x。由于 fn(x)=x,所以 gn(x)=F(x)

推导一下,有:

gi(fi(x))=f(x)=gi+1(fi+1(x))gi(|aifi+1(x)|)=gi+1(fi+1(x))gi+1(x)=gi(|aix|)

大力将绝对值拆开,有:

gi+1(x)={gi(aix),x[0,ai]gi(xai),x>ai

那么 gi+1 本质上就是将 gi 图像向右平移了 ai 格,并令 x[0,ai),gi+1(x)=gi(2aix)

直接大力维护,复杂度 O(m+ai)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 1000010
#define ll long long
using namespace std;
int a[N],n,m;ll f[N],s[N];
vector<int>res;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
    int w=s[n-1]-s[2];
    res.reserve(w*2);
    for(int i=0;i<=w;i++) res.push_back(w-i);
    for(int i=3;i<n;i++)
    {
        int t=res.size()-1;
        for(int j=1;j<=a[i];j++) res.push_back(res[t-j]);
    }
    reverse(res.begin(),res.end());
    scanf("%d",&m);
    while(m --> 0)
    {
        int x;scanf("%d",&x);
        if(x>w) printf("%d\n",x-w+a[1]-a[2]);
        else printf("%d\n",res[x]+a[1]-a[2]);
    }
    return 0;
}

I. Reverse Grid

首先如果 n 是奇数,那么最中间的一行的翻转与其他行无关,特殊处理。m 同理。不妨假设 n,m 都是偶数。

考虑矩阵中的每一个格子只能出现在 4 个位置上,不妨把这四个位置拿出来,要使得矩阵的其他位置不交换,只能令行交换和列交换次数都是偶数。容易发现:如果四个位置字母互不相同,这样可以使这四个位置在逆序对奇偶性不变下任意排列,即 12 种方案;如果存在字母相同,因为可以任意交换相同的字母,所以方案数就是四个字母的排列。

但上面考虑的都是所有行列交换次数都为偶数的情况。由于第 i 行和第 ni+1 行的等价的,不妨只考虑 in2 的行与 jm2 的列。容易发现如果存在点 (i,j) 对应四个字母互不相同,i 行翻转或者 j 列翻转之后,(i,j) 的奇偶性都会改变。

对行列分别建二分图,每个点点权 {0,1},相当于问有多少种给边赋点权的方式,使每条边的权值是其两端点权异或和。容易发现对于每个连通块,每个节点点权完全取反。

用并查集,复杂度 O(nm)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 210
#define mod 1000000007
using namespace std;
char s[N][N];int c[26];const int fac[]={1,1,2,6,24,120};
int f[N*2],g[N*2],_2[N*2];
int find(int x){return x==f[x]?f[x]:(f[x]=find(f[x]));}
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
    for(int i=_2[0]=1;i<=n+m;i++) _2[i]=_2[i-1]*2ll%mod;
    int n0=n,m0=m;n/=2,m/=2;
    int res=1;
    if(n0&1){bool r0=false;for(int i=1;i<=m;i++) if(s[n+1][i]!=s[n+1][m0-i+1]) r0=true;if(r0) res*=2;}
    if(m0&1){bool r0=false;for(int i=1;i<=n;i++) if(s[i][m+1]!=s[n0-i+1][m+1]) r0=true;if(r0) res*=2;}
    for(int i=1;i<=n+m;i++) f[i]=i;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            memset(c,0,sizeof(c));
            c[s[i][j]-'a']++,c[s[n0-i+1][j]-'a']++,c[s[i][m0-j+1]-'a']++,c[s[n0-i+1][m0-j+1]-'a']++;
            int r=fac[4];
            for(int p=0;p<26;p++) r/=fac[c[p]];
            if(r!=24) res=1ll*res*r%mod;
            else
            {
                res=12ll*res%mod;
                f[find(i)]=find(j+n);
            }
        }
    for(int i=1;i<=n+m;i++) g[find(i)]++;
    for(int i=1;i<=n+m;i++) if(f[i]==i) res=1ll*res*_2[g[i]-1]%mod;
    printf("%d",res);
    return 0;
}
posted @   Flying2018  阅读(118)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示