上周打完多校队内开了个会商量了一下每个人的学习计划和接下来的比赛状态。于是这个专题开始了,还有几道题待做,先把题解写一下吧。
我是看大白书学的,挺好理解的,不过前提是要学过线性代数,这样理解起来比较容易,如果没学过可能会有点困难。鄙人愚见:高斯消元类似小学学过的解二元方程组,那么方程组至少要有两个以上才会有解,这很好理解的吧。但求解多元方程组就不那么容易了,而线性代数里面的求解过程就是将系数矩阵和常数矩阵放在一起构成的增广矩阵把原系数矩阵化成上三角矩阵,那么这个增广矩阵的原来的常数矩阵这一部分就是解了。而在化成上三角矩阵的这个过程就是高斯消元啦。注意浮点数的计算误差问题。
常见类型:开关问题、直接消元求解问题及自由元的处理问题。
高斯消元
EXTENDED LIGHTS OUT
题意:给你一个5*6的01矩阵,1表示开着,0表示关着,按动一个开关会将其本身的周围的开关的状态都改变,求将所有开关都关上的一种方案。
高斯消元的异或版本,初态是给出的原始矩阵,终态是全部为0。由初态到终态也可以反着来,所以将初始矩阵作为终态,每个开关的状态仅由其本身和周围四个开关决定,那么每个开关都可以列一个方程了。直接求解即可。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/A/0/
开关问题
题意:和上面的类似,但给出的是初态和终态,然后给出开关之间的关系,求由初态到终态有多少种方案。
做过上一题这个题基本问题不大,我们求方案数只需看有多少个自由元然后答案就是(1<<free_num)。只是建方程俗称建图方式有点不同的,注意这个如果初态和终态状态不一致说明这个开关要改变状态,反之不用。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/B/0/
Widget Factory
题意:有n种商品,m个工厂,每个工厂生产k个商品,已知每种商品的生产时间范围,然后给出了每个工厂的生产开始时间与截止时间,求这n种商品每种的生产时间。
这道题是看模板做出来的,题目相当于m个同余方程组,求未知变量。对于解同余方程组的高斯消元并没有什么想法。。。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/C/0/
Painter's Problem
题意:和开关问题类似,只是将操作换成了染色而已,求将n*n的方格全部染成黄色的最小操作数。
这道题需要枚举自由元,如果解唯一直接累加计数需要改变状态的开关即可,但有多解的情况就需要用枚举来确定某一行的状态进而确定所有格子的状态,然后更新最优解即可。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/D/0/
SETI
题意:给你一个模数p和长度为n的字符串。然后对于每个字符a[i]给出计算方式。求系数矩阵。
除了题意有点难懂其他的就是一个裸的高斯消元了。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/E/0/
Flip Game
题意:还是开关问题,按动一个开关会将其本身及周围开关的状态都改变,求将初态全部变成一种状态(全白或全黑)的最小操作数。
有了上面的题的基础很容易知道需要枚举自由变元,由于涉及两种状态,需要用两个不同的系数矩阵来存,传参的时候注意一下就好了。。。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/F/0/
The Water Bowls
题意:和POJ-2065类似,还是开关问题,只会影响两边的开关,求全部变为0的最小操作数。
建图建方程式判断每个开关的初态也可以作为终态。枚举自由元更新最小值。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/G/0/
Kind of a Blur
题意:还是高斯消元裸题,建图稍微有点麻烦,每个点的影响范围是与其曼哈顿距离不超过d的点,给出每个点的计算方式和终态矩阵,求初始矩阵。
暴力建图后裸的高斯消元就过了,犯了个sb错误一直PE。。
代码:https://cn.vjudge.net/contest/176398#status/liuyuqiang0/I/0/
以上大部分题都不难,开关类问题较多,今天下午卡在一道用线性基求解的题上,明天补上。
XOR
(好题)题意:给你n个数,Q次查询,每次一个k,求这些数的所有异或组合的第k小。
这道题在高斯消元专题里面,但是上手却毫无思路,百度题解才知道要用到一个什么线性基。于是花了一下午去学了一下,这道题要用线性基来优化。高斯消元可以在64*64*n的复杂度内求出n个数的线性基。但也有64*n的方法求。只要将线性基求出来之后,排序。求第k小只需将k转化为二进制,那么第k小即这些二进制位为1的位对应的数的异或组合。这样复杂度就是64*n。
const int N=1e5+5;
const int maxn=500+5;
ll dp[63],p[63],x,tot;
int n,q,cnt;
void get()
{
cnt=0;
scanf("%d",&n);
memset(dp,0,sizeof(dp));
for(int i=0; i<n; i++)
{
scanf("%lld",&x);
for(int j=62; j>=0; j--)
{
if(!(x>>j)) continue;//这一位没有贡献
if(!dp[j]) { dp[j]=x; break; }
x^=dp[j];
}
}
for(int i=62; i>=0; i--)
for(int j=i-1; j>=0; j--)//变成线性无关
if(dp[i]&(ll(1)<<j)) dp[i]^=dp[j];
sort(dp,dp+63);
for(int i=0; i<63; i++)
if(dp[i])
p[cnt++]=dp[i];
tot=ll(1)<<cnt;
if(cnt==n) tot--;//0不能取到
}
void query()
{
ll k,res=0;
scanf("%lld",&k);
if(k>tot) puts("-1");
else
{
if(cnt<n) k--;//除去0;
for(int i=0; i<63; i++) if(k&(ll(1)<<i)) res^=p[i];
printf("%lld\n",res);
}
}
int main()
{
int t;
scanf("%d",&t);
int t1=t;
while(t--)
{
get();
scanf("%d",&q);
printf("Case #%d:\n",t1-t);
while(q--) query();
}
return 0;
}