UOJ 75 [UR #6]智商锁

随机化+矩阵树定理

好神的做法(官方题解)。。。怒膜jry

一直陷在基尔霍夫矩阵的坑里,以为能通过一些数学技巧反推出合理地矩阵?真是naive。

一个重要的思路是:一张图的生成树个数等于把桥边割掉之后剩下的所有子图的生成树个数之积。 这是显然的,毕竟桥边不会贡献答案,而环的贡献可以分开考虑。

发现这玩意儿可以乘之后就可以随机化乱搞了。随机出1000个n=12的小连通块,则生成树个数为 [0,1210] , 最大值远大于998244353。在模意义下等价于均匀分布随机数。再暴力枚举四个块(双搜),判断它们接起来答案是不是k即可。证明详见官方题解。

突然忘记多维指针怎么用,于是代码常数突然变大。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MOD 998244353
#define HMOD 3333331
using namespace std;
namespace runzhe2000
{
    typedef unsigned uint;
    typedef long long ll;
    uint randint()// WC2017式随机数 
    {
        static uint R = 1;
        R ^= (R << 13);
        R ^= (R >> 17);
        R ^= (R << 5); 
        return R;
    }
    int rand01(double p)
    {
        return randint() % MOD < (MOD * p);
    }
    int abs(int a){return a<0?-a:a;}
    const int t = 1000, n = 12;
    struct node{int next, val, id;}no[t*t*2];
    int d[t+5][n+5][n+5], f[t+5], head[HMOD], hcnt, dd[n+5][n+5];
    int hash_find(int val)
    {
        int hash = val % HMOD;
        for(int i = head[hash]; i; i = no[i].next)
            if(no[i].val == val) return no[i].id;
        return 0;
    }
    void hash_ins(int val, int id)
    {
        int hash = val % HMOD;
        no[++hcnt] = (node){head[hash], val, id};
        head[hash] = hcnt;
    }
    int fpow(int a, int b)
    {
        int r = 1;
        for(; b; b>>=1)
        {
            if(b&1)r=(ll)r*a%MOD;
            a=(ll)a*a%MOD;
        }
        return r;
    }
    int calc(int id)
    {
        int det = 1, nn = n-1;
        for(int i = 1; i <= nn; i++)
            for(int j = 1; j <= nn; j++)
                dd[i][j] = d[id][i][j];
        for(int i = 1; i <= nn; i++)
        {
            int p = i;
            for(int j = i+1; j <= nn; j++) if(abs(dd[j][i]) > abs(dd[p][i])) p = j;
            if(p != i){for(int j = 1; j <= nn; j++) swap(dd[i][j], dd[p][j]);det*=-1;} 
            if(!dd[i][i]) return 0;
            int inv = fpow(dd[i][i], MOD - 2);
            for(int j = i+1; j <= nn; j++)
            {
                int base = (ll)dd[j][i] * inv % MOD;
                for(int k = i; k <= nn; k++)
                    dd[j][k] = (dd[j][k] - (ll)dd[i][k] * base % MOD) % MOD;
            }
            det = (ll) det * dd[i][i] % MOD;
        }
        return (det + MOD ) % MOD;
    }
    int out[n*n*4][2], m;
    void print(int id, int base)
    {
        for(int i = 1; i <= n; i++)
            for(int j = i+1; j <= n; j++)
                if(d[id][i][j]) ++m, out[m][0] = i+base, out[m][1] = j+base;
    }
    void print_make()
    {
        printf(" %d\n",m);
        for(int i = 1; i <= m; i++) printf("%d %d\n",out[i][0], out[i][1]);
        m = 0;
    }
    void solve(int K)
    {
        for(int i = 1; i <= t; i++) if(f[i]) 
            for(int j = i+1; j <= t; j++) if(f[j])
            { 
                int val = (ll) f[i] * f[j] % MOD, tmp;
                if((tmp = hash_find((ll)K * fpow(val, MOD - 2) % MOD)))
                {
                    int k = tmp / 2333, l = tmp % 2333;
                    printf("%d",4*n);
                    print(i, 0); print(j, n); print(k, n*2); print(l, n*3);
                    out[++m][0] = 1; out[m][1] = n+1;
                    out[++m][0] = n+1; out[m][1] = 2*n+1;
                    out[++m][0] = 2*n+1; out[m][1] = 3*n+1;
                    print_make();
                    return;
                }
                hash_ins(val, i*2333+j);
            }
    }
    void main()
    {
        for(int i = 1; i <= t; i++) 
        {
            for(int j = 1; j <= n; j++)
                for(int k = j+1; k <= n; k++)
                    if((d[i][j][k] = -rand01(0.5)))
                    {
                        d[i][k][j] = -1;
                        d[i][j][j]++, d[i][k][k]++;
                    }
            f[i] = (calc(i) + MOD) % MOD;
        }
        int T, k; scanf("%d",&T);
        for(; T--; )
        {
            scanf("%d",&k);
            if(!k) {puts("2 0");continue;}
            memset(head,0,sizeof(head));
            hcnt = 0;
            solve(k);
        }
    }
}
int main()
{
    runzhe2000::main();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值