线性代数 专场 暨 一周年纪念博客

前言

正好今天学习OI一整年,发篇博客纪念纪念。

听说联赛现在全国排名?那完了,一年OI一场空。

记得去年这个时候刚学OI,跟那些初中就开始学的大佬听课,第一节他们就讲2-sat,第二节就是线性代数。

我的内心是

然后,我就自闭了一个月。

如今又来看这些玄学玩意儿。

先列一下知识点(显得我更像是抄课件了呢)

线性基
  高斯消元
  行列式(主要是矩阵树定理)
  矩阵的幂(邻接矩阵、矩阵加速递推)

(以后可能会写总结吧,咕,我在)

还记得上一个专场是什么吗?

打表找规律,乱搞出奇迹,我对拍了17分钟你能秒我?

我们又来看一道博弈(×)找规律(√)的题目

HYSBZ    3105    新Nim游戏

题目大意

依然是n堆石子

第一个回合,先手可以直接拿走若干个整堆的石子。可以一堆都不拿,但不可以全部拿走。
  第二回合也一样,后手也有这样一次机会。
  从第三个回合开始(又轮到先手)的规则和nim游戏一样。
  问先手是否有必胜策略,如果有,还要让先手拿走的石子总数尽量小。

解析

我们想一想nim游戏是先手必败的情形是什么?

一般nim游戏的必败条件是所有数异或为0。

所以,我们无论如何也不能让后拿的人有这个机会

而如果先拿的人剩下的石子堆中存在一些石子堆的数目异或起来为0,也就是它的某个子集满足nim游戏的必败条件

那么第二个人只需要把其它的石子堆拿走第一个人就必败,反之第一个人必胜。

所以我们必须保证第一个人剩下的石子堆的任意子集异或起来不为0

在线性代数中,我们称剩下的数在二进制位上线性无关

题目也就转化成了从n个数中选一些数,使得它们在二进制位上线性无关,且和最大

这题就是求和最大的线性基,只需要排序一下再选就好了,有点像最小生成树

推荐一个线性基的博客

注意开longlong

代码

#include<cstdio>
#include<algorithm>
using namespace std;
int n,vis[105],a[105],b[55];long long ans;
bool insert(int val)
{
    for(int i=30;i>=0;i--)
        if(val&(1<<i))
        {
            if(!b[i]){b[i]=val;break;}
            val^=b[i];
        }
    return val>0;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for(int i=n;i>=1;i--)vis[i]=insert(a[i]);
    for(int i=1;i<=n;i++)ans+=vis[i]?0:a[i];
    printf("%lld\n",ans);
}

正文 线性代数

线性基篇

HDU    3949    XOR

题目大意

给定一些数,求这些数通过异或能得到的数中的第k小是多少。

解析

好像是线性基的板题。线性基能组合出来的数的个数是2^(向量个数),把k做二进制拆分就好了

代码

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
vector<int>g;int T,n,m,q;long long a,A[105];
void insert(long long a)
{
    for(int i=60;i>=0&&a;i--)
    {
        if(!((1ll<<i&a)))continue;
        if(A[i]){a^=A[i];continue;}
        for(int j=0;j<i;j++)if(a&(1ll<<j))a^=A[j];
        for(int j=i+1;j<=60;j++)if(A[j]&(1ll<<i))A[j]^=a;
        A[i]=a;break;
    }
}
int main()
{
    scanf("%d",&T);
    for(int it=1;it<=T;it++)
    {
        memset(A,0,sizeof A);g.clear();m=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%lld",&a),insert(a);
        for(int i=0;i<=60;i++)if(A[i])g.push_back(A[i]),m++;
        printf("Case #%d:\n",it);scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            long long k;scanf("%lld",&k);
            k-=(m!=n);
            if((1ll<<m)-1ll<k)printf("-1\n");
            else
            {
                long long ans=0;
                for(int i=0;i<m;i++)if((1ll<<i)&k)ans^=g[i];
                printf("%lld\n",ans);
            }
        }
    }
}

高斯消元篇

HYSBZ    3143    游走

题目大意

n个点,m条边的连通无向图,边的权值从1到n
  一开始在1号点,每次等概率走向当前点的邻接点
  问怎样给边赋权值,使得走到n号点时期望走过的边权和最小

又是课件的解析

走到n号点时期望走过的边权和=每条边 期望走过次数*边权 的和
  边(u,v)期望走过次数=点u期望走过次数/u的度数+点v期望走过次数/v的度数
  即E((u,v))=E(u)/deg(u)+E(v)/deg(v)
  设v是u的邻接点,再考虑E(u)和E(v)之间的关系

E(1)=1+sum_v E(v)/deg(v) (v!=n,v是1的邻接点)
  E(u)=sum_v E(v)/deg(v) (1<u<n,v!=n,v是u的邻接点)
  E(n)=0
  n-1个方程,n-1个未知数,高斯消元一发

最后贪心
  按照边走过的期望次数赋权值
  边走过的期望次数越少,就赋越大的权值
  代码

#include<cstdio>
#include<cstring>
#define maxn 505
#include<algorithm>
using namespace std;
int n,m,deg[maxn],lin[maxn*maxn][2];
double eq[maxn][maxn],E[maxn],El[maxn*maxn];
bool no(double x){return -1e-10<x&&x<1e-10;}
void Gauss()
{
    for(int i=1;i<=n;i++)
    {
        if(no(eq[i][i])){for(int j=i+1;j<=n;j++)if(!no(eq[j][i])){swap(eq[i],eq[j]);break;}}
        for(int j=1;j<=n;j++)if(j!=i&&!no(eq[j][i]))
        {
            double t=eq[j][i]/eq[i][i];
            for(int k=1;k<=n+1;k++)eq[j][k]=eq[j][k]-eq[i][k]*t;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)scanf("%d%d",&lin[i][0],&lin[i][1]),deg[lin[i][0]]++,deg[lin[i][1]]++;
    eq[n][n]=1.000;eq[1][n+1]=-1.0;
    for(int i=1;i<n;i++)eq[i][i]=-1.0;
    for(int i=1;i<=m;i++)if(lin[i][0]!=n&&lin[i][1]!=n)
    eq[lin[i][0]][lin[i][1]]=1.0/double(deg[lin[i][1]]),eq[lin[i][1]][lin[i][0]]=1.0/double(deg[lin[i][0]]);
    Gauss();
    for(int i=1;i<=n;i++)E[i]=eq[i][n+1]/eq[i][i];
    for(int i=1;i<=m;i++)El[i]=E[lin[i][0]]/double(deg[lin[i][0]])+E[lin[i][1]]/double(deg[lin[i][1]]);
    sort(El+1,El+1+m);
    double ans=0;
    for(int i=1;i<=m;i++)ans+=El[i]*double(m-i+1);
    printf("%.3lf\n",ans);return 0;
}

行列式篇

CodeForces    167E    Wizards and Bets

题目大意

保证源点和汇点数目相同。
  考虑所有把源汇点两两配对,并用两两不相交的路径把它们两两连接起来的所有方案。
  如果这个方案中,把源点按标号1到n排序后,得到的对应汇点序列的逆序数对的个数是奇数,那么A给B一块钱,否则B给A一块钱。
  问最后A的收益,对大质数取模。
  n ≤ 600

感觉有些玄学的解析

先不考虑不相交路径的限制
  令f[i][j]为从第i个无入度的点走到第j个无出度的点的方案数
  由行列式的定义,这个矩阵的行列式的值就是答案(每一项前的正负由逆序对个数的奇偶性决定)
  再考虑路径不相交的情形,发现答案不变
  这是因为对于任一种两条路径相交的方案x,选择这对路径上相交的最后一个点,将这个点之后的路径反转,一定会映射到另一种路径相交的方案y

且方案y对答案的贡献刚好和x对答案的贡献相反(逆序数奇偶发生改变)

f[i][j]可以用拓补序来dp转移(好像是我第一次写)

代码

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=605,maxm=100005;
int n,m,cnt,be,en,en1,en2,ans,mod,flag,S[maxn],T[maxn],f[maxn][maxn];
int v[maxm],in[maxn],nex[maxm],out[maxn],top[maxn],info[maxn],eq[maxn][maxn];
void add(int u,int v1){nex[++cnt]=info[u];info[u]=cnt;v[cnt]=v1;}
int qp(int a,int k){int res=1;while(k){if(k&1)res=1ll*res*a%mod;k>>=1;a=1ll*a*a%mod;}return res;}
void Gauss()
{
    for(int i=1;i<=en1;i++)
    {
        if(!eq[i][i]){for(int j=i+1;j<=en1;j++)if(eq[j][i]){swap(eq[i],eq[j]);ans*=-1;break;}}
        if(!eq[i][i]){flag=1;return;}int inv=qp(eq[i][i],mod-2);
        for(int j=1;j<=en1;j++)if(i!=j&&eq[j][i])
        {
            int t=1ll*eq[j][i]*inv%mod;if(!t)continue;
            for(int k=1;k<=en1;k++)eq[j][k]=(1ll*mod+1ll*eq[j][k]-1ll*eq[i][k]*t)%mod;
        }
    }
    for(int i=1;i<=en1;i++)ans=1ll*ans*eq[i][i]%mod;
}
int main()
{
    scanf("%d%d%d",&n,&m,&mod);
    for(int i=1,u,v1;i<=m;i++)scanf("%d%d",&u,&v1),add(u,v1),out[u]++,in[v1]++;
    for(int i=1;i<=n;i++){if(in[i]==0)S[++en1]=i,top[++en]=i,f[i][i]=1;if(out[i]==0)T[++en2]=i;}
    while(be<en)
    {
        int x=top[++be];for(int i=info[x];i;i=nex[i]){in[v[i]]--;if(!in[v[i]])top[++en]=v[i];}
        for(int i=1;i<=en1;i++)for(int j=info[x];j;j=nex[j])(f[S[i]][v[j]]+=f[S[i]][x])%=mod;
    }
    for(int i=1;i<=en1;i++)for(int j=1;j<=en2;j++)eq[i][j]=f[S[i]][T[j]];
    ans=1;Gauss();
    printf("%d",flag?0:(ans+mod)%mod);
}

矩阵树定理篇

HYSBZ     3534    重建

题目大意

每条边都有一定概率p∈[0,1]出现在图中
  求生成一棵树的概率是多少

解析

答案应该是(原谅我不会数学公式)

即在生成树中的边出现的概率乘上不在生成树中的边不出现的概率

由变元矩阵树定理,矩阵树定理求的是权值积的和,即

接下来我们想如何把那些不选其它边的概率一起计算了,

即用在生成树上的边的一些信息表示不在生成树上的边不出现的概率。

注意到,显然上面那个分子对于每组数据来说是个常数。

所以答案可以变换为

然后就可以用矩阵树定理做了
  注意处理当pe=1的情况,赋值成一个很接近1的数就好了

代码

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const double eps=1e-7;
int n;double mart[55][55],ans=1.0;
bool no(double x){return fabs(x)<eps;}
double Gauss()
{
    double ret(1.0);int N(n-1),f(0);
    for(int i=1;i<=N;i++)
    {
        if(no(mart[i][i])){for(int j=i+1;j<=N;j++)if(!no(mart[j][i])){swap(mart[i],mart[j]);f^=1;break;}}
        if(no(mart[i][i]))return 0.0;
        for(int j=1;j<=N;j++)if(i!=j)
        {
            double t=mart[j][i]/mart[i][i];
            for(int k=1;k<=N;k++)mart[j][k]-=mart[i][k]*t;
        }
        ret*=mart[i][i];
    }
    if(f)ret=-ret;
    return ret;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%lf",&mart[i][j]);
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
    {
        if(no(mart[i][j]))mart[i][j]=eps;
        if(no(1-mart[i][j]))mart[i][j]=1.0-eps;
        if(i<j)ans*=1.0-mart[i][j];
        mart[i][j]/=(1.0-mart[i][j]);
    }
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
    if(i!=j)mart[i][i]+=mart[i][j],mart[i][j]=-mart[i][j];
    ans*=Gauss();
    printf("%.10lf\n",ans);
}

矩阵快速幂篇

HDU    4686    Arc of Dream

题目大意

a0 = A0
  ai = ai-1*AX+AY
  b0 = B0
  bi = bi-1*BX+BY
  AoD(n)=\sum_{i=0}^{n-1} aibi
  答案mod 1e9+7
  n<=10^18,其他数(A0,AX,AY,B0,BX,BY)<=2*10^9

总算是自己写的解析

看到n的范围就容易想到矩阵快速幂,来考虑一下如何转移

求AoD(n)需要AoD(n-1)和anbn,所以这两项必须有

而求anbn,因为矩阵乘法原始矩阵各项之间只能相加,

所以anbn只能由an-1bn-1转移来。

anbn=(an-1*Ax+Ay)*(bn-1*Bx+By)

=Ax*Bx*an-1bn-1+AxBy*an-1+AyBx*bn-1+AyBy

所以还需要一个常数项1,an,bn,转移矩阵用Ax,Ay,Bx,By,1来填就好了

代码(压行压得像篇英语作文似的)

#include<cstdio>
#include<cstring>
const int mod=1000000007;
long long n,A0,B0,Ax,Bx,Ay,By;
struct node{long long mart[10][10];}ans,g;
node operator *(node a,node b)
{
    node c;memset(c.mart,0,sizeof c.mart);
    for(int i=1;i<=5;i++)for(int j=1;j<=5;j++)for(int k=1;k<=5;k++)
    (c.mart[i][j]+=1ll*a.mart[i][k]*b.mart[k][j]%mod)%=mod;
    return c;
}
int main()
{
    while(scanf("%lld%lld%lld%lld%lld%lld%lld",&n,&A0,&Ax,&Ay,&B0,&Bx,&By)!=EOF)
    {
        if(n==0){printf("0\n");continue;}
        memset(g.mart,0,sizeof g.mart);memset(ans.mart,0,sizeof ans.mart);
        g.mart[1][1]=1;g.mart[2][1]=Ax*Bx%mod;g.mart[3][1]=Ax*By%mod;g.mart[4][1]=Ay*Bx%mod;g.mart[5][1]=Ay*By%mod;
        g.mart[2][2]=Ax*Bx%mod;g.mart[3][2]=Ax*By%mod;g.mart[4][2]=Ay*Bx%mod;g.mart[5][2]=Ay*By%mod;
        g.mart[3][3]=Ax%mod;g.mart[5][3]=Ay%mod;g.mart[4][4]=Bx%mod;g.mart[5][4]=By%mod;g.mart[5][5]=1;
        ans.mart[1][1]=A0*B0%mod;ans.mart[1][2]=A0*B0%mod;ans.mart[1][3]=A0%mod;ans.mart[1][4]=B0%mod;ans.mart[1][5]=1;
        n--;
        while(n)
        {
            if(n&1)ans=ans*g;
            n>>=1;g=g*g;
        }
        printf("%lld\n",ans.mart[1][1]);
    }
}

原文地址:https://www.cnblogs.com/firecrazy/p/11441448.html

时间: 2024-11-08 13:13:08

线性代数 专场 暨 一周年纪念博客的相关文章

纪念下今天,开始写博客了

还有一年大学毕业,借着剩余稍微空闲的时间,总结下自己所学.从专注学习web前端算起来快有一年了. 前前后后经历了许多.在web这个领域,除了自学意外,程序员之间口口相传才能深入学习,总结是必不可少的.希望自己能坚持下去 纪念下今天,开始写博客了,布布扣,bubuko.com

funtoo 安装手册阅读 (博客第 100 篇文章纪念)

*/--> pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;} pre.src {background-color: Black; color: White;}

不忘初心,历久弥坚 —— 以博客申请 纪念 码系团诞生

# -*- coding: utf-8 -*- vocation  = '程序猿' interest  = 'Python and C' print "记录自己的成长历程,温故而知新\n" print "写作的过程是一个总结的过程,在斟酌笔迹的时候,模糊的知识渐渐明晰\n" print "当我在前人栽下的大树下乘凉时,理所当然的希望自己栽下的小树可以参天\n" print '苦恼于<百度知道>的博大“阱”深,为什么不把你的经验分享在一

51CTO博客旧版首页截图纪念

2017年7月11日,51CTO博客PC端首页进行改版,这个曾经陪伴了多年的旧版首页已经超期服役,将告别历史舞台,但它也承载了我们很多作者的光荣与梦想.承载了我们很多阅读的时光.特将旧版首页截图保存,以作留念.

纪念第一次写博客

2019 04 07 第一次写博客就记录最基础的c语言下的利用循环 使用时间戳的猜数字游戏 vs  实现 #define _CRT_SECURE_NO_WARNINGS   //  vs   下的宏定义    (scanf) #include<stdio.h> #include<stdlib.h> #include<string.h> #include <time.h> int Menu() { int choice = 0; printf("1,

Python爬取CSDN博客文章

之前解析出问题,刚刚看到,这次仔细审查了 0 url :http://blog.csdn.net/youyou1543724847/article/details/52818339Redis一点基础的东西目录 1.基础底层数据结构 2.windows下环境搭建 3.java里连接redis数据库 4.关于认证 5.redis高级功能总结1.基础底层数据结构1.1.简单动态字符串SDS定义: ...47分钟前1 url :http://blog.csdn.net/youyou1543724847/

博客奖品之 【征文活动】奔跑中的2015

活动:[征文活动]奔跑中的2015 一等奖  豪华礼物 (1名,二者任选其一)  51cto课程学习卡(15天): 任意收费课程为期15天免费学习,你可以选择你喜爱的老师的视频观看. 使用规则:一张学习卡包含一个学习码.仅限一 个51cto账号.免费观看一个收费课程,不可重复使用,使用后15天内可免费观看51cto学院提供的收费课程. 小米(MI)手环: 智能防水运动手环,计步器,可监测健康睡眠(黑色原封). 小米手环帮你记录全天活动,计算行走距离及热量消耗,也可以设定目标,运动健康生活. 二等

学习博客 启动日记

今天,1月27日,应该是我值得纪念的一个日子,我决定真正开始学习android开发并坚决要做出成绩的日子. 以前,自己总是犹豫不决,停留在想,未必真的去做.这次,我决定我要放手一搏.为了以后的幸福,为了支持我,懂我的人,为了那些一直陪伴我的人我觉得我应该放手一搏. 我现在一直在跟着极客出的安卓开发教学视频,我打算以两套视频为基础,吃透一部,另一部作为补充.这样,我希望自己能够在很短的时间内掌握安卓开发的能力. 说到做学习博客,我还是一直没有底的,因为毕竟自己是新手,很多地方都不懂.会不会误导大家

【μ&#39;sic forever???】μ&#39;s Final Love Live周年纪念

一.正文 "切なくて时をまきもどしてみるかい?No no no--いまが最高!" 转眼就是一周年了,其实fl后入坑的我在这里怀念显得有些无病呻吟.但我也有想说的话,说给重要的人听. "ほのかな予感から始まり",是她们,将无人问津的434变成了9.6万,让毫不起眼的1023化作了淹没数十万人的橙色海洋.六年间,她们从Oricon周榜164位,一步一步地登上了榜首:她们的观众,从最开始的可怜的1700人,增长到填满东京巨蛋和全球251个转播场的数十万人.超过10万张的单