zoukankan      html  css  js  c++  java
  • 【LibreOJ】#6259. 「CodePlus 2017 12 月赛」白金元首与独舞

    【题目】给定n行m列的矩阵,每个位置有一个指示方向(上下左右)或没有指示方向(任意选择),要求给未定格(没有指示方向的位置)确定方向,使得从任意一个开始走都可以都出矩阵,求方案数。n,m<=200,k<=300(未定格数量)。

    【算法】生成树计数(矩阵树定理)

    【题解】先对定向格DFS找环判断是否无解。

    然后每个点向指示方向连边,未定格向四周连边,外界作为一个点。

    将所有有向边反向后,就是求根为外界的树形图的数量,生成树计数问题用矩阵树定理解决。

    复杂度T*O((n*m)^3)。

    考虑优化,发现重要的只有未定格的方向且k很小,所以将未定格之间的路径直接计算后只保留未定格的图,再用矩阵树定理解决。

    复杂度T*O(k^3)。

    注意:

    1.逆元的log放在外面,否则复杂度会变成O(k^3*log k)。

    2.因为要取模,高斯消元前必须全部变成正数,且矩阵每次交换两行都会使答案取反。

    3.有重边,邻接矩阵不能赋值为1,有向边度数矩阵只计算入度。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=310,MOD=1e9+7;
    const int fx[]={0,0,1,-1};
    const int fy[]={1,-1,0,0};
    struct edge{int v,from;}e[maxn*2];
    struct node{int x,y;}b[maxn];
    int n,m,vis[maxn][maxn],v[maxn][maxn],tot,t[maxn][maxn],a[maxn][maxn];
    char s[maxn];
    bool ok;
    void dfs(node u,int mark){
        if(v[u.x][u.y]||u.x<1||u.x>n||u.y<1||u.y>m)return;
        vis[u.x][u.y]=mark;
        int X=u.x+fx[t[u.x][u.y]],Y=u.y+fy[t[u.x][u.y]];
        if(vis[X][Y]==mark)ok=0;
        else if(vis[X][Y])return;
        else dfs((node){X,Y},mark);
    }
    void insert(int u,int v){
        a[v][v]++;a[u][v]--;//
    }
    void gcd(int a,int b,int& x,int& y){
        if(!b){x=1;y=0;}
        else{gcd(b,a%b,y,x);y-=x*(a/b);}
    }
    int inv(int a){
        int x,y;
        gcd(a,MOD,x,y);
        return (x%MOD+MOD)%MOD;
    }
    int gauss(int n){
        bool f=0;
        for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)a[i][j]=(a[i][j]+MOD)%MOD;
        for(int i=1;i<=n;i++){
            int r=i;
            for(int j=i+1;j<=n;j++)if(a[j][i]>a[r][i])r=j;
            if(r!=i){for(int j=i;j<=n+1;j++)swap(a[i][j],a[r][j]);f^=1;}
            int w=inv(a[i][i]);//
            for(int j=i+1;j<=n;j++){
                for(int k=n+1;k>=i;k--){
                    a[j][k]=(a[j][k]-1ll*a[j][i]*w%MOD*a[i][k]%MOD+MOD)%MOD;//
                }
            }
        }
        int ans=f?MOD-1:1;
        for(int i=1;i<=n;i++)ans=1ll*ans*a[i][i]%MOD;
        return ans;//
    }
    int main(){
        int T;scanf("%d",&T);
        while(T--){
            memset(v,0,sizeof(v));
            memset(vis,0,sizeof(vis));
            memset(a,0,sizeof(a));
            tot=0;
            scanf("%d%d",&n,&m);
            for(int i=1;i<=n;i++){
                scanf("%s",s+1);
                for(int j=1;j<=m;j++){
                    if(s[j]=='U')t[i][j]=3;
                    if(s[j]=='D')t[i][j]=2;
                    if(s[j]=='L')t[i][j]=1;
                    if(s[j]=='R')t[i][j]=0;
                    if(s[j]=='.'){v[i][j]=++tot;b[tot]=(node){i,j};}
                }
            }
            ok=1;
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++)if(!vis[i][j])dfs((node){i,j},(i-1)*m+j);
                if(!ok)break;
            }
            if(!ok){puts("0");continue;}
            tot++;
            for(int i=1;i<tot;i++){
                int x=b[i].x,y=b[i].y;
                for(int j=0;j<4;j++){
                    int X=x+fx[j],Y=y+fy[j];bool yes=0;
                    while(X>=1&&X<=n&&Y>=1&&Y<=m){
                        if(v[X][Y]){if(v[X][Y]!=i)insert(v[X][Y],i);yes=1;break;}
                        else{
                            int o1=fx[t[X][Y]],o2=fy[t[X][Y]];
                            X+=o1;Y+=o2;
                        }
                    }
                    if(!yes)insert(tot,i);
                }
            }
            printf("%d
    ",gauss(tot-1));
        }
        return 0;
    }
    View Code
  • 相关阅读:
    进制的转换
    输出蛇型矩阵
    输出弓形矩阵
    找出一个数组中出现次数最多的那个元素
    开灯问题
    find your present
    核反应堆
    Box of Bricks最小移动砖块数目
    超级楼梯
    Bootstrap中的 JavaScript 特效 — 下拉菜单和滚动监听插件
  • 原文地址:https://www.cnblogs.com/onioncyc/p/8137971.html
Copyright © 2011-2022 走看看