中山纪中集训Day1测试

AT3 粉刷匠
Description
赫克托是一个魁梧的粉刷匠,而且非常喜欢思考= =
现在,神庙里有N根排列成一直线的石柱,从1到N标号,长老要求用油漆将这些石柱重新粉刷一遍。赫克托有K桶颜色各不相同的油漆,第i桶油漆恰好可以粉刷Ci根石柱,并且,C1+C2+C3…CK=N(即粉刷N根石柱正好用完所有的油漆)。长老为了刁难赫克托,要求相邻的石柱颜色不能相同。
喜欢思考的赫克托不仅没有立刻开始粉刷,反而开始琢磨一些奇怪的问题,比如,一共有多少种粉刷的方案?
为了让赫克托尽快开始粉刷,请你尽快告诉他答案。
Input
第一行一个正整数T,表示测试数据组数
对于每一组测试数据数据:
第1行:一个正整数K
第2行:K个正整数,表示第i桶油漆可以粉刷的石柱个数,Ci。
Output
对于每组输入数据,输出一行一个整数,表示粉刷的方案数mod 1000000007。
Sample Input
3
3
1 2 3
5
2 2 2 2 2
10
1 1 2 2 3 3 4 4 5 5
Sample Output
10
39480
85937576
Data Constraint
30% N≤10, T≤5
50% N≤15, T≤5
80% K≤15,Ci≤5,T≤500
100% K≤15,Ci≤6,T≤2000
以为是纯数学计算的我看了一眼就放弃。
分析
我们不妨换一个角度来理解题目。我们可以先将石柱染色,再将石柱进行排列。
我们设f[i][j]表示前i种颜色的石柱全部拿来排列后,有j根石柱它们的颜色与它们左边的那个石柱相同。
填表法不是很好做,所以我们直接考虑刷表法。
对于每一个状态f[i][j],因为每种颜色的个数是已知的,所以当前石柱所组成的序列的长度是已知的,设为sum。
设第i种颜色石柱的数量是a[i],那么从f[i]转移到f[i+1],将有a[i+1]个石柱将插入到序列里面。
显然,插入时会使原来在一起的两块石柱分开的这种插入会影响到状态本身,所以应该将它单独讨论。
设插入时会插入到p个两边颜色相同的空,q个其他情况的空,那么p+q<=sum+1且p+q<=a[i+1],还剩下a[i+1]-p-q个石柱的插入,它们必然会产生新的颜色相同且相邻的石柱。
那么f[i][j]就可以转移到f[i+1][j-p+a[i+1]-p-q],p与q都可以枚举。将a[i+1]个球放入p+q个空中且不能不放,根据隔板法可知要乘上C(a[i+1]-1,p+q-1).又要在j个两边颜色相同的空中选p个则又要乘上C(j,p),还要在剩下sum+1-j个空中选q个,就还要乘上C(sum+1-j,q)。
直接贴代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#define mod 1000000007
using namespace std;
int C[105][105],f[105][105],T,n,a,sum;
int main()
{
    C[0][0]=1;
    for(int i=1;i<=105;i++)
    {
        C[i][0]=1;
        for(int j=1;j<=i;j++)
        C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);sum=0;f[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a);
            for(int j=0;j<=sum+a+1;j++)f[i][j]=0;
            for(int j=0;j<=sum;j++)
            {
                for(int p=0;p<=min(j,a);p++)
                {
                    for(int q=0;q<=min(sum+1-j,a-p);q++)
                    {
                        if(!q&&!p)continue;
                        long long ans=f[i-1][j];int nex=j-p+a-p-q;
                        (ans*=1ll*C[a-1][p+q-1])%=mod;
                        (ans*=1ll*C[j][p])%=mod;
                        (ans*=1ll*C[sum+1-j][q])%=mod;
                        (f[i][nex]+=ans)%=mod;
                    }
                }
            }
            sum+=a;
        }
        printf("%d\n",f[n][0]);
    }
}

AT2 Vani和Cl2捉迷藏
Description
vani和cl2在一片树林里捉迷藏……
这片树林里有N座房子,M条有向道路,组成了一张有向无环图。
树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。如果从房子A沿着路走下去能够到达B,那么在A和B里的人是能够相互望见的。
现在cl2要在这N座房子里选择K座作为藏身点,同时vani也专挑cl2作为藏身点的房子进去寻找,为了避免被vani看见,cl2要求这K个藏身点的任意两个之间都没有路径相连。
为了让vani更难找到自己,cl2想知道最多能选出多少个藏身点?
Input
第一行两个整数N,M。
接下来M行每行两个整数x、y,表示一条从x到y的有向道路。
Output
一个整数K,表示最多能选取的藏身点个数。
Sample Input
4 4
1 2
3 2
3 4
4 2
Sample Output
2
Data Constraint
对于20% 的数据,N≤10,M<=20。
对于60% 的数据, N≤100,M<=1000。
对于100% 的数据,N≤200,M<=30000,1<=x,y<=N。
分析
选择一个藏身点后就会有一些路径被废掉不能再选。
然后听说这是求DAG的最小不相交路径覆盖?将原图的点拆成x部与y部,若a能到达b则连Xa指向Yb。
然后DAG的最小路径覆盖=原图的结点数-对应二分图的最大匹配数。(????)
个人理解(乱理解):每一条边代表了原图中的一条路径,每一个匹配代表匹配边两边的点被连在一起,即这条路径上有一个点被选择,整条路径将不再可用。因为求的是最大匹配,所以没有匹配的点在原图中就是一条路径的终点,这些点都可作为藏身点,故答案为原图的结点数-对应二分图的最大匹配数。
代码:

#include<cstdio>
#include<cstring>
int n,m,ans,mat[205],vis[205],V[205][205];
int solve(int x)
{
    for(int i=1;i<=n;i++)
    {
        if(V[x][i]&&!vis[i])
        {
            vis[i]=1;
            if(!mat[i]||solve(mat[i]))
            {
                mat[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<=m;i++)scanf("%d%d",&u,&v),V[u][v]=1;
    for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
    if(i!=j)V[i][j]=V[i][j]||(V[i][k]&&V[k][j]);
    for(int i=1;i<=n;i++)
    {
        memset(vis,0,sizeof vis);
        ans+=solve(i);
    }
    printf("%d\n",n-ans);
}

AT1 水叮当的舞步
Description
水叮当得到了一块五颜六色的格子形地毯作为生日礼物,更加特别的是,地毯上格子的颜色还能随着踩踏而改变。
为了讨好她的偶像虹猫,水叮当决定在地毯上跳一支轻盈的舞来卖萌~~~
地毯上的格子有N行N列,每个格子用一个0~5之间的数字代表它的颜色。
水叮当可以随意选择一个0~5之间的颜色,然后轻轻地跳动一步,地毯左上角的格子所在的联通块里的所有格子就会变成她选择的那种颜色。这里连通定义为:两个格子有公共边,并且颜色相同。
由于水叮当是施展轻功来跳舞的,为了不消耗过多的真气,她想知道最少要多少步才能把所有格子的颜色变成一样的。
Input
每个测试点包含多组数据。
每组数据的第一行是一个整数N,表示地摊上的格子有N行N列。
接下来一个NN的矩阵,矩阵中的每个数都在0~5之间,描述了每个格子的颜色。
N=0代表输入的结束。
Output
对于每组数据,输出一个整数,表示最少步数。
Sample Input
2
0 0
0 0
3
0 1 2
1 1 2
2 2 1
0
Sample Output
0
3
Data Constraint
对于30%的数据,N<=5
对于50%的数据,N<=6
对于70%的数据,N<=7
对于100%的数据,N<=8,每个测试点不多于20组数据。
分析
没看完虹猫蓝兔的我肯定不会做这个题。
观察数据范围,n最大也就8,直接暴力出奇迹,剪枝后复杂度玄学。
先从小到大枚举最大搜索深度(听说这叫迭代加深?),注意不能二分,因为这是指数级别的复杂度增长。
然后进行两个剪枝,一个是变色前统计一下左上角联通块附近有哪几种颜色,变的时候就只用变成这些颜色。
另一个是当 当前深度 加上 剩下联通块的颜色的种类数 大于我们枚举的最大搜索深度时可以直接return 0(听说这跟A
有关?);
写的时候注意局部变量与全局变量,不能混在一起。
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,tmp,I,color[15],ma[15][15],vis[15][15],vised[15][15],used[15];
int cnt1(){int ans=0;for(int i=1;i<=6;i++)ans+=(color[i]>0);return ans;}
void cnt2(int x,int y)
{
    if(ma[x][y]==-1||vised[x][y])return;
    if(ma[x][y]){used[ma[x][y]]=1;return;}vised[x][y]=1;
    cnt2(x+1,y);cnt2(x-1,y);cnt2(x,y+1);cnt2(x,y-1);
}
void print(int x,int y,int c,int dep)
{
    if(ma[x][y]==-1||(ma[x][y]!=c&&ma[x][y]!=0)||vised[x][y])return;vised[x][y]=1;
    if(ma[x][y]==c)ma[x][y]=0,color[c]--,vis[x][y]=dep;
    print(x+1,y,c,dep);print(x-1,y,c,dep);print(x,y+1,c,dep);print(x,y-1,c,dep);
}
bool solve(int noww,int t)
{
    if(noww+cnt1()-1>t)return 0;//这个地方的1减去会多搜索,不减不严谨
    if(1==(color[1]>0)+(color[2]>0)+(color[3]>0)+(color[4]>0)+(color[5]>0)+(color[6]>0))return 1;
    memset(vised,0,sizeof vised);memset(used,0,sizeof used);
    I=noww;cnt2(1,1);int u[15]={0};memcpy(u,used,sizeof u);
    for(int i=1;i<=6;i++)
    {   
        if(!u[i])continue;
        memset(vised,0,sizeof vised);
        print(1,1,i,noww);
        if(solve(noww+1,t))return 1;
        for(int i1=1;i1<=n;i1++)for(int j=1;j<=n;j++){if(vis[i1][j]==noww)color[ma[i1][j]=i]++,vis[i1][j]=-1;}
    }
    return 0;
}
int main()
{
    memset(ma,-1,sizeof ma);
    while(scanf("%d",&n)&&n)
    {
        memset(color,0,sizeof color);
        memset(vis,-1,sizeof vis);
        memset(ma,-1,sizeof ma);
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        scanf("%d",&ma[i][j]),color[++ma[i][j]]++;
        for(int i=0;i<=n*n;i++)
        if(solve(0,i)){printf("%d\n",i);break;}
    
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值