• BZOJ 1004: [HNOI2008]Cards


    1004: [HNOI2008]Cards

    题意:有n张卡片,染成s1张红色,s2张蓝色和s3张绿色;之后有m种置换关系,问本质不同的染色方案有多少种?

    Input

    第一行输入 5 个整数:Sr,Sb,Sg,m,p(m<=60,m+1<p<100)。n=Sr+Sb+Sg。接下来 m 行,每行描述
    一种洗牌法,每行有 n 个用空格隔开的整数 X1X2...Xn,恰为 1 到 n 的一个排列,表示使用这种洗牌法,
    第i位变为原来的Xi位的牌。输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代替,且对每种
    洗牌法,都存在一种洗牌法使得能回到原状态。

    Output

    不同染法除以P的余数

    Sample Input

    1 1 1 2 7
    2 3 1
    3 1 2

    Sample Output

    2

    HINT

    有2 种本质上不同的染色法RGB 和RBG,使用洗牌法231 一次可得GBR 和BGR,使用洗牌法312 一次 可得BRG 和GRB。

    100%数据满足 Max{Sr,Sb,Sg}<=20。

    思路:对于输入的置换,先使用循环分解算法分解出cnt个循环,并且得出每个循环里面的元素个数为num;

    若是没有mod操作,直接就是所有置换f的K^(m(f))之和的平均值

    K表示颜色数,m(f)表示置换f将序列循环分解后的循环节数(即不相交循环的个数);

    这是因为在置换的作用下,每个循环内的元素的颜色必定相同;平均数即总的置换数;

    一个隐含的置换就是不操作,f[i] = i;

    难点:题目对每种颜色染色的数量做了规定,由于每个循环内所有元素的颜色是相同的,那么这个循环就必须作为一个整体进行染色;所以在01背包中,每个循环的大小就相当于物品的重量;注意每种颜色都是可能将一个循环染色的即可;

    在求出mod下的sum之后,将sum/m变成mod意义下的加法;

    即m*x = sum(mod p)   ==> m*x + p*y = sum(在exgcd()中求出的是 = gcd(m,p) = 1);

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<string.h>
    #include<algorithm>
    #include<vector>
    #include<cmath>
    #include<stdlib.h>
    #include<time.h>
    #include<stack>
    #include<set>
    #include<map>
    #include<queue>
    using namespace std;
    #define rep0(i,l,r) for(int i = (l);i < (r);i++)
    #define rep1(i,l,r) for(int i = (l);i <= (r);i++)
    #define rep_0(i,r,l) for(int i = (r);i > (l);i--)
    #define rep_1(i,r,l) for(int i = (r);i >= (l);i--)
    #define MS0(a) memset(a,0,sizeof(a))
    #define MS1(a) memset(a,-1,sizeof(a))
    #define MSi(a) memset(a,0x3f,sizeof(a))
    #define inf 0x3f3f3f3f
    #define lson l, m, rt << 1
    #define rson m+1, r, rt << 1|1
    //typedef __int64 ll;
    template<typename T>
    void read1(T &m)
    {
        T x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        m = x*f;
    }
    template<typename T>
    void read2(T &a,T &b){read1(a);read1(b);}
    template<typename T>
    void read3(T &a,T &b,T &c){read1(a);read1(b);read1(c);}
    template<typename T>
    void out(T a)
    {
        if(a>9) out(a/10);
        putchar(a%10+'0');
    }
    int s1,s2,s3,p,m,n;
    int vis[70],f[70][70][70],num[70],B[70][70];
    int DP(int id)
    {
        MS0(vis);
        int cnt = 0;
        rep1(i,1,n)if(!vis[i]){//循环的分解
            num[++cnt] = 0;
            int tmp = i;
            do{
                vis[tmp] = cnt;
                num[cnt]++;
                tmp = B[id][tmp];
            }while(!vis[tmp]);
        }
        MS0(f);
        f[0][0][0] = 1;
        rep1(i,1,cnt){
            rep_1(j,s1,0)
                rep_1(k,s2,0)
                    rep_1(q,s3,0){
                        if(j >= num[i]) (f[j][k][q] += f[j-num[i]][k][q]) %= p;
                        if(k >= num[i]) (f[j][k][q] += f[j][k-num[i]][q]) %= p;
                        if(q >= num[i]) (f[j][k][q] += f[j][k][q-num[i]]) %= p;
                }
        }
        return f[s1][s2][s3];
    }
    void exgcd(int a,int b,int& x,int& y)
    {
        if(b == 0){
             x = 1,y = 0;//等式右边为gcd(a,p) = 1;此时a = gcd(a,p);
             return ;
        }
        exgcd(b,a%b,y,x);
        y -= a/b*x;
    }
    
    int main()
    {
        read3(s1,s2,s3);
        read2(m,p);
        n = s1 + s2 + s3;
        rep1(i,1,m) rep1(j,1,n) read1(B[i][j]);
        ++m;//设置没变化的置换;
        rep1(i,1,n) B[m][i] = i;
        int sum = 0;
        rep1(i,1,m) sum += DP(i);//记录所有置换f的K^m(f)的和
        int x,y;// ans = sum/m;是在没有mod的时候
        exgcd(m,p,x,y);
        while(x < 0) x += p,y -= m;
        printf("%d
    ",sum*x%p);
        return 0;
    }
    View Code
  • 相关阅读:
    JavaScript单线程和浏览器事件循环简述
    Promise的前世今生和妙用技巧
    自定义Angular插件
    smartcrop.js智能图片裁剪库
    判断是否安装微博
    Java 注解
    android tools使用方式
    listview复用机制研究
    java 驼峰字符和下划线字符相互转换工具类
    剪切板(复制、粘贴)工具类
  • 原文地址:https://www.cnblogs.com/hxer/p/5222893.html
Copyright © 2020-2023  润新知