【CSP模拟赛】Freda的旗帜


题目描述
  要开运动会了,Freda承担起了制作全校旗帜的工作。旗帜的制作方法是这样的:Freda一共有C种颜色的布条,每种布条都有无数个,你可以认为这些布条的长、宽、厚都相等,只有颜色可能不同。每个旗帜都是由一些布条横向拼接起来的,如图所示,图上所示的是一面红、黄、蓝三种颜色布条拼接的旗帜:

  就不给你看图

  布条数目不同的旗帜显然是不同的。对于布条数目都为T的两面旗帜,如果存在从左到右第i(0<i<=N)个布条颜色不同,那么就认为这两面旗帜是不同的。旋转或翻转后才相同的旗帜被认为是不同的旗帜,比方说,“红黄蓝”和“蓝黄红”被认为是不同的旗帜。有的时候,一些颜色放在相邻的位置上会显得很不好看,比方说红色和绿色放在一起就很不好看。作为一个完美主义者,Freda可不想这样。
  全校共有N个班级,不同的班级必须使用不同的旗帜,Freda也就需要制作N面不同的旗帜了。和谐起见,Freda想让使用布条数目最多的那面旗帜使用的布条数目最少,请你帮助Freda计算一下,在避免了不好看的情况之后,使用布条数目最多的那面旗帜最少要用多少布条。
输入格式
  第一行两个整数N,C,表示班级的数目和Freda拥有C种颜色的布条。
  接下来C行,每行一个长度为C的字符串,每个字符都为’0’或’1’,第i行第j个字符表示第i种颜色和第j种颜色是否能够相邻。如果能,第i行第j个字符为’1’,否则为’0’.保证第i行第j个字符与第j行第i个字符相同。
输出格式
  一行一个整数,表示制作了N面不同的旗帜的情况下,使用布条数目最多的那面旗帜最少需要多少布条。
输入样例
  10 2
  11
  11
输出样例
  3
提示
样例解释
  我们分别制作如下旗帜:
  (1),(2),(1,1),(1,2),(2,1),(2,2),(1,1,1),(1,1,2),(1,2,1),(1,2,2)
  用布条最多的旗帜所用布条为3.
数据范围与约定
  对于50%的数据,保证0 < N <= 50000.
  对于80%的数据,保证0 < N <= 10^9.
  对于100%的数据,保证0 < N <= 10^18, 0 < C <=10且数据一定有解.

分析

  这个题也想了半天,其实是因为睡着了

  设f[i][j]表示有旗帜长度为i的时候,第j种颜色开头的方案数,V[i][j]表示i后面是否可以接j

  那么就有

  $$f[i][j]=\sum_{k=1}^{c}f[i-1][k]*V[j][k]$$

  发现这个东西可以反手一个矩阵加速闷声发大财,所以对于每一个长度,我们可以很快求出这个长度下的方案数

  而且我们还可以发现长度与方案数是单调递增的,所以可以二分长度,求出这个长度下的方案数的总和,看它是否能大于要求的方案数n

  当然,方案数统计出来可能会炸longlong,我们可以重新定义一个类似longlong的结构体,结构体里除了longlong还要再来一个标记,标记这个数是否超过了n

  Code

#include<cstdio>
#include<cstring>
long long n;int c;
struct ll{long long num;bool fl;};
struct node{ll mat[15][15];}a,g;
ll operator +(ll a,ll b){return (ll){a.num+b.num,a.fl||b.fl||(a.num+b.num>=n)};}
ll operator *(ll a,ll b)
{
    if((a.num==0&&!a.fl)||(b.num==0&&!b.fl))return (ll){0,0};
    return (ll){a.num*b.num,a.fl||b.fl||(double(a.num)*double(b.num)>=double(n))};
}
node operator *(node a,node b)
{
    node res;memset(res.mat,0,sizeof res.mat);
    for(int i=1;i<=c+1;i++)for(int j=1;j<=c+1;j++)for(int k=1;k<=c+1;k++)
    res.mat[i][k]=res.mat[i][k]+a.mat[i][j]*b.mat[j][k];
    return res;
}
node qp(node a,node g,long long k)
{while(k){if(k&1)a=a*g;g=g*g;k>>=1;}return a;}
int main()
{
    scanf("%lld%d",&n,&c);
    for(int i=1;i<=c;i++)for(int j=1;j<=c;j++)scanf("%1lld",&g.mat[i][j].num);
    for(int i=1;i<=c;i++)a.mat[1][i].num=1,g.mat[i][c+1].num=1;g.mat[c+1][c+1].num=1;
    long long l=1,r=n,mid,ans;
    while(l<=r)
    {
        mid=(l+r)>>1;
        node nw=qp(a,g,mid);ll sum=nw.mat[1][c+1];
        if(sum.fl)ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
}

猜你喜欢

转载自www.cnblogs.com/firecrazy/p/11792190.html