Codeforces Gym 101201 部分题题解

D.Contest Strategy

题目链接

Contest Strategy

简要题解

大家都明白,这大概率是一个\(n^3\)的Dp题或数学题,不过知道了这个也不是很好想。
观察题目,我们不难发现,可以对每一个\(t_i\)单独计算贡献,只要知道它是第几个被切掉的题。
因为从它被切掉开始,后面的每一道题的罚时都要加上\(t_i\)
所以我们只要知道有多少个序列使得\(t_i\)在某个位置被切掉,就能算出我们需要的答案。
首先需要对\(t_i\)排序,对于\(t_i\)相同的题,我们可以任意规定一个大小关系,这样计算答案时又方便又不会重复或遗漏。
直观的想法是,设\(F[i][j]\)表示,有多少个序列,使得第\(i\)小的数,是第\(j\)道被切掉的题。
这个不好维护,至少我不会。但是对于这样的Dp式,我们往往可以转化成一个更好求,且效果完全相同的式子。
我们重新定义\(F[i][j]\)为,使得第\(i\)小的数,是前\(j\)道被切掉的题,的序列数。
那么考虑如何求这个式子。

这里需要发现题目的性质:如果某个数在前\(j\)道题中被切掉,那么它一定是前\(j+K-1\)道题中前\(j\)小的题。
证明不难,每次切掉\(K\)道题中最小的那一道,那么至少有\(K-1\)题比它大。
所以我们只需要保证,第\(i\)小的数是前\(K+j-1\)个数中前\(j\)小的数就能满足限制。
可以直接枚举第\(i\)个数具体在前\(K+j-1\)个数中是第几小,然后选一些比它小的,再选一些比它大的构成前\(K+j-1\)个数的集合。
注意我们求的是序列,而不是集合,所以要乘上阶乘。
\(k\)表示枚举到第\(i\)个数在前\(K+j-1\)个数中是第\(k\)小,令\(P=Min(K+j-1,n)\),总结出来的公式如下:

\[F[i][j]=\sum_{k=0}^{j-1}C_{i-1}^k*C_{n-i}^{P-k-1}*P!*(n-P)! \]

引入\(P\)是因为,我们只有\(n\)道题,做到后面题目都看完了。
时间复杂度\(O(n^3)\)
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=310;
const int Mod=1e9+7;
int n,K,Ans,A[MAXN],Fac[MAXN];
int F[MAXN][MAXN],C[MAXN][MAXN];
int Min(int A,int B){   return A<B?A:B;   }
int main()
{   scanf("%d%d",&n,&K),C[0][0]=Fac[0]=1;
    for(int i=1;i<=n;i++) scanf("%d",&A[i]),Fac[i]=1ll*Fac[i-1]*i%Mod;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++) C[i][j]=(j?(C[i-1][j]+C[i-1][j-1])%Mod:1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=0,P;k<=j-1;k++)
                P=Min(n,K+j-1),F[i][j]=(F[i][j]+1ll*C[i-1][k]*C[n-i][P-k-1]%Mod*Fac[P]%Mod*Fac[n-P])%Mod;
    sort(A+1,A+n+1);
    for(int i=1;i<=n;i++)
        for(int j=n;j>=1;j--) F[i][j]=(1ll*F[i][j]+Mod-F[i][j-1])%Mod;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) Ans=(Ans+1ll*F[i][j]*A[i]%Mod*(n-j+1))%Mod;
    printf("%d\n",Ans);
}

G.Maximum Islands

题目链接

Maximum Islands

简要题解

首先很明显的一点是,我们需要将已经出现的\(L\)周围全部放上\(W\),因为在\(L\)旁边放\(L\)不会增加答案,反而占用了空间。
放好之后我们剩下了一些\(C\),接下来只需要对这些\(C\)来作考虑。
为了使连通块尽量多,我们需要使连通块的大小尽量小,自然大小为\(1\)是最优的,所以我们不应该让两个\(L\)相邻,并且每一个\(L\)都会产生\(1\)的贡献。
那么问题变成了这个:给定网格图中的一些位置,在这些位置上取出尽量多的点,使得取出的点中两两不相邻。
这很像是一个最大独立集,于是我们可以利用网格图黑白染色的技巧,使得相邻点不同色,并把相邻点连边,就形成了一个二分图。
现在就是要求这个二分图的最大独立集,而二分图最大独立集=二分图点数-最大匹配数
因此我们求一个最大匹配就好了,这里采用了匈牙利算法。
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5010;
struct EDGE{   int u,v,Next;   }Edge[MAXN*2];
char M[70][70];
int n,m,Ans,Es,First[MAXN],Cp[MAXN],Vis[MAXN];
int Id(int H,int L){   return (H-1)*m+L;   }
void Link(int u,int v){   Edge[++Es]=(EDGE){u,v,First[u]},First[u]=Es;   }
namespace PRE
{   bool Vis[70][70];
    void Dfs(int Nh,int Nl)
    {   Vis[Nh][Nl]=1;
        if(!Vis[Nh-1][Nl]&&M[Nh-1][Nl]=='L') Dfs(Nh-1,Nl);
        if(!Vis[Nh+1][Nl]&&M[Nh+1][Nl]=='L') Dfs(Nh+1,Nl);
        if(!Vis[Nh][Nl-1]&&M[Nh][Nl-1]=='L') Dfs(Nh,Nl-1);
        if(!Vis[Nh][Nl+1]&&M[Nh][Nl+1]=='L') Dfs(Nh,Nl+1);
    }
    void Prepare()
    {   for(int i=0;i<=n+1;i++) M[i][0]=M[i][m+1]='W';
        for(int i=0;i<=m+1;i++) M[0][i]=M[n+1][i]='W';
        for(int i=1;i<=n;i++)
            for(int j=1,Nd,Ld;j<=m;j++)
            {   if(M[i][j]=='L'&&!Vis[i][j]) Dfs(i,j),Ans++;
                if(M[i][j]!='C') continue ;
                Ans++,Nd=Id(i,j);
                if(M[i][j-1]=='C') Ld=Id(i,j-1),Link(Ld,Nd),Link(Nd,Ld);
                if(M[i-1][j]=='C') Ld=Id(i-1,j),Link(Ld,Nd),Link(Nd,Ld);
            }
    }
}using PRE::Prepare;
void Round()
{   for(int i=1;i<=n;i++)
        for(int j=1,L;j<=m;j++)
        {   if(M[i][j]!='C') continue ;
            L=(M[i-1][j]=='L')+(M[i+1][j]=='L')+(M[i][j-1]=='L')+(M[i][j+1]=='L');
            if(L) M[i][j]='W';
        }
}
bool Match(int Now,int Nc)
{   for(int i=First[Now],v,Flag;i!=-1;i=Edge[i].Next)
    {   v=Edge[i].v,Flag=0,Vis[v]==Nc?Flag=1:Vis[v]=Nc;
        if(!Flag&&(!Cp[v]||Match(Cp[v],Nc))) return Cp[Now]=v,Cp[v]=Now,1;
    }
    return 0;
}
int main()
{   scanf("%d%d",&n,&m);
    memset(First,-1,sizeof(First));
    for(int i=1;i<=n;i++) cin>>M[i]+1;
    Round(),Prepare();
    for(int i=1;i<=n*m;i++) if(!Cp[i]) Ans-=Match(i,i);
    printf("%d\n",Ans);
}

L.Windy Path

题目链接

Windy Path

简要题解

我们的限制是,要使得序列中连续的三个点满足特定的顺时针或逆时针要求。
假设我们现在有了第\(i\)个点,如果我们对于第\(i\)个位置上的限制,牵扯到了\(i+1\)\(i+2\)两个点,那么情况会非常复杂,影响后续决策,想不明白。
因此我们想知道,有没有这样的点,当它作为第\(i+1\)个点时,无论第\(i+2\)个点是什么,都能满足第\(i\)个位置上的限制。
实际上是有的,我们假设第\(i\)个限制是逆时针。
对于第\(i\)个点和当前还没有加入序列的点,我们做一个最大的凸包,且满足第\(i\)个点在凸包上。
不难发现,若第\(i+1\)个点是第\(i\)个点逆时针方向上的下一个点,那么第\(i\)个限制一定会得到满足。
所以这题的解法就很清楚了,首先找一个在凸包上的点,然后根据限制选择凸包上顺时针或逆时针的下一个点,然后更新凸包。
实际上,并不需要求凸包,我们枚举目标点,利用向量叉积就可以作出判断。
由于\(n<=50\),所以想怎么写就怎么写。
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=60;
const double Pai=acos(-1);
struct DOT{   int X,Y,Id;   }Dot[MAXN],Vec;
char Dir[MAXN];
int n,St,Get[MAXN];
DOT operator - (DOT A,DOT B){   return (DOT){A.X-B.X,A.Y-B.Y,A.Id-B.Id};   }
int Min(int A,int B){   return A<B?A:B;   }
int Max(int A,int B){   return A>B?A:B;   }
int Cross(DOT A,DOT B){   return A.X*B.Y-A.Y*B.X;   }
int Check(int Now,int Nt,int Nd)
{   int Mins=1e9,Maxs=-1e9,Nc;
    for(int i=1;i<=n;i++)
        if(!Get[i]&&i!=Nt) Nc=Cross(Dot[i]-Dot[Now],Dot[Nt]-Dot[Now]),Mins=Min(Mins,Nc),Maxs=Max(Maxs,Nc);
    return Nd=='R'?Mins>=0:Maxs<=0;
}
int Find(int Now,int Nd)
{   for(int i=1;i<=n;i++)
        if(!Get[i]&&Check(Now,i,Nd)) return i;
    return 0;
}
int main()
{   scanf("%d",&n),St=1;
    for(int i=1;i<=n;i++) scanf("%d%d",&Dot[i].X,&Dot[i].Y),Dot[i].Id=i;
    scanf("%s",Dir+1);
    for(int i=2;i<=n;i++)
        if(Dot[i].X<Dot[St].X) St=i;
    for(int i=1;i<=n-2;i++) printf("%d ",St),Get[St]=1,St=Find(St,Dir[i]);
    printf("%d ",St),Get[St]=1;
    for(int i=1;i<=n;i++)
        if(!Get[i]) printf("%d\n",i);
}

posted @ 2021-07-30 17:06  Alkaid~  阅读(164)  评论(0编辑  收藏  举报