高斯消元
北文 讲的。
用 \(O(n^3)\) 的复杂度求解线性方程组。
约旦消元法:
-
选择一个未选的未知数为主元,选择一个包含该主元的方程。
-
将该方程的主元系数化为 \(1\) .
-
通过加减消元消掉其他方程中的这个未知数。
-
重复上述步骤。
操作完之后的方程组长这样:
将等号右边的值除以系数即为所求。
存在 \(c_i=0,d_i\not=0\) 时无解。
此外存在 \(c_i=0,d_i=0\) 时有无数组解。
不存在唯一解输出 No Solution
.
#define N 105
#define db double
using namespace std;
int n;
db a[N][N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++)
scanf("%lf",&a[i][j]);
for(int i=1;i<=n;i++){
int mx=i;
for(int j=i+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
for(int j=1;j<=n+1;j++)
swap(a[i][j],a[mx][j]);
if(!a[i][i]){
printf("No Solution\n");
return 0;
}
for(int j=1;j<=n;j++){
if(j==i)continue;
db tp=a[j][i]/a[i][i];
for(int k=i+1;k<=n+1;k++)
a[j][k]-=a[i][k]*tp;
}
}
for(int i=1;i<=n;i++)
printf("%.2lf\n",a[i][n+1]/a[i][i]);
return 0;
}
同样是板子题。
有点不一样写了半天。
#include<bits/stdc++.h>
#define db double
#define N 55
using namespace std;
int n,cur;
db a[N][N];
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++)
for(int j=0;j<n+1;j++)
scanf("%lf",&a[i][j]);
for(int i=0;i<n;i++){
int mx=cur;
for(int j=cur+1;j<n;j++)
if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
if(!a[mx][i])continue;
for(int j=0;j<n+1;j++)
swap(a[cur][j],a[mx][j]);
for(int j=0;j<n;j++){
if(j==cur)continue;
db tp=a[j][i]/a[cur][i];
for(int k=i+1;k<n+1;k++)
a[j][k]-=a[cur][k]*tp;
}
cur++;
}
if(cur<n){
for(int i=cur;i<n;i++)
if(a[i][n]){
printf("-1\n");
return 0;
}
printf("0\n");
return 0;
}
for(int i=0;i<n;i++)
printf("x%d=%.2lf\n",i+1,a[i][n]/a[i][i]);
return 0;
}
一个无向图,节点初始为 \(0\) .
一次操作可以使一个点及其相邻点权值 \(\text{xor}\space 1\) .
问使 \(n\) 个点都变为 \(1\) 的最小步数。
也就是求解异或方程组。
异或与加乘法都满足交换、结合律,可以按照高消的过程写。
会消出来一些自由元,暴力回代剪枝 \(\text{dfs}\) .
每题高消写的都不太一样。
只能说有一定道理。
#include<bits/stdc++.h>
#define N 40
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m;
int a[N][N],ans=N;
int f[N];
void gauss(){
for(int i=1;i<=n;i++){
int mx=i;
for(int j=i+1;j<=n;j++)
if(a[j][i]){mx=j;break;}
for(int j=1;j<=n+1;j++)
swap(a[mx][j],a[i][j]);
for(int j=i+1;j<=n;j++)
if(a[j][i]){
for(int k=1;k<=n+1;k++)
a[j][k]^=a[i][k];
}
}
}
void dfs(int x,int tot){
if(tot>ans)return;
if(!x){
ans=min(ans,tot);
return;
}
if(a[x][x]){
f[x]=a[x][n+1];
for(int i=n;i>x;i--)
f[x]^=f[i]&a[x][i];
dfs(x-1,tot+f[x]);
}
else{
f[x]=0,dfs(x-1,tot);
f[x]=1,dfs(x-1,tot+1);
}
}
int main(){
n=read(),m=read();
for(int u,v;m;m--){
u=read(),v=read();
a[u][v]=a[v][u]=1;
}
for(int i=1;i<=n;i++)
a[i][i]=a[i][n+1]=1;
gauss(),dfs(n,0);
printf("%d\n",ans);
return 0;
}
顺次加入异或方程组,判断何时存在唯一解。
每次消元都要向下找一个最小 \(a[j][i]=1\) ,于是便可满足题目要求。
贺一下看看,用 bitset
优化。
#include<bits/stdc++.h>
#define N 1010
#define M 2010
#define bs bitset<N>
using namespace std;
int read(){
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x;
}
int gets(){
char ch=getchar();
while(!isdigit(ch))ch=getchar();
return ch-'0';
}
int n,m;
bs a[M];
int ans;
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
for(int j=1;j<=n+1;j++)
a[i][j]=gets();
}
for(int i=1;i<=n;i++){
int cur=i;
while(cur<=m&&!a[cur][i])cur++;
if(cur==m+1){
printf("Cannot Determine\n");
return 0;
}
ans=max(ans,cur);
swap(a[cur],a[i]);
for(int j=1;j<=m;j++){
if(j==i)continue;
if(a[j][i])a[j]^=a[i];
}
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)
printf(a[i][n+1]?"?y7M#\n":"Earth\n");
return 0;
}
一个点会向若干其他点连单向边。
将它们分成两部分,使得每一个点的部分中都有偶数个与其连边的点。
还原编号或输出无解。
\(n\le 2000\) .
对点 \(i\) 在上下游设状态 \(0,1\) .
对于点 \(i\) 有
即
那么 \(a_i\) 这位就可能取 \(0,1\) ,利用 bitset
求解异或方程组即可。
#include<bits/stdc++.h>
#define N 2010
using namespace std;
int read(){
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x;
}
int n,m;
bitset<N>a[N];
int ans,cnt;
int main(){
n=read();
for(int i=1;i<=n;i++){
m=read();
a[i][i]=a[i][n+1]=m&1;
while(m--)
a[i][read()]=1;
}
for(int i=1;i<=n;i++){
int mx=i;
for(int j=mx;j<=n;j++)
if(a[j][i]){mx=j;break;}
swap(a[mx],a[i]);
if(!a[i][i])continue;
cnt++;
for(int j=1;j<=n;j++)
if(j!=i&&a[j][i])a[j]^=a[i];
}
if(cnt<n){
for(int i=cnt+1;i<=n;i++)
if(!a[i][i]&&a[i][n+1]){
printf("Impossible\n");
return 0;
}
}
for(int i=1;i<=n;i++)
ans+=a[i][n+1];
printf("%d\n",ans);
for(int i=1;i<=n;i++)
if(a[i][n+1])printf("%d ",i);
printf("\n");
return 0;
}
高斯消元优化dp放这么难?
英雄当前血量 \(p\) ,上限血量 \(n\) ,有 \(m\) 个血量为 \(\text{inf}\) 的随从,不存在上限血量。
每次给随机一位非满血的友方角色治疗 \(1\) 点,接着执行 \(k\) 次操作,使随机一位友方角色扣 \(1\) 点血。
问期望几次治疗后英雄寄掉。
\(T\le 100\) ,\(p,n\le 1500\) ,\(0\le m,k\le 10^9\) .
设 \(P(i)\) 为扣 \(i\) 滴血的概率,有
设 \(f(i)\) 为血量为 \(i\) 的期望次数。
转移是
注意 \(\forall i>k,P(i)\rightarrow 0\) .
那么只需要处理 \(n+1\) 个 \(P\) ,递推式
化一下
边界 \(f(0)=0\) .
长得就像高斯消元了,但是 \(O(n^3)\) 不可过。
做的时候发现每次只消剩两个数,也就是只需要处理这两列。
时间复杂度 \(O(n^2)\) .
这题特判比较多。
#include<bits/stdc++.h>
#define N 1505
#define Mod 1000000007
using namespace std;
int read(){
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x;
}
int qpow(int k,int b,int p){
int ret=1;k%=p;
while(b){
if(b&1)ret=1ll*ret*k%p;
k=1ll*k*k%p,b>>=1;
}
return ret;
}
int inv(int x){
return qpow(x,Mod-2,Mod);
}
int T,n,p,m,k;
int im,im1,P[N];
int a[N][N],iv[N];
void init(){
for(int i=1;i<N;i++)
iv[i]=inv(i);
}
int main(){
T=read(),init();
while(T--){
n=read(),p=read(),m=read(),k=read();
if(!k){
printf("-1\n");
continue;
}
if(!m){
if(k==1)printf("-1\n");
else{
if(p!=n)printf("%d\n",(p-1)/(k-1)+1);
else{
p-=k,p=p>0?(p-1)/(k-1)+1:0;
printf("%d\n",p+1);
}
}
continue;
}
memset(P,0,sizeof(P));
memset(a,0,sizeof(a));
im=inv(m),im1=inv(m+1),P[0]=qpow(1ll*m*im1%Mod,k,Mod);
for(int i=1;i<=min(n,k);i++)
P[i]=1ll*P[i-1]*(k-i+1)%Mod*iv[i]%Mod*im%Mod;
for(int i=1;i<n;i++){
for(int j=1;j<=i;j++)
a[i][j]=(1ll*P[i+1-j]+1ll*m*P[i-j])%Mod*im1%Mod;
a[i][i+1]=1ll*P[0]*im1%Mod,(a[i][i]+=Mod-1)%=Mod,a[i][n+1]=Mod-1;
}
for(int i=1;i<=n;i++)
a[n][i]=P[n-i];
(a[n][n]+=(Mod-1))%=Mod,a[n][n+1]=Mod-1;
for(int i=1,tiv;i<=n;i++){
if(!a[i][i])continue;
tiv=inv(a[i][i]);
for(int j=1;j<=n;j++){
if(i==j||!a[j][i])continue;
int tp=1ll*a[j][i]*tiv%Mod;
if(i<n)a[j][i+1]=(1ll*a[j][i+1]-1ll*a[i][i+1]*tp%Mod+Mod)%Mod;
a[j][n+1]=(1ll*a[j][n+1]-1ll*a[i][n+1]*tp%Mod+Mod)%Mod;
}
}
printf("%d\n",1ll*a[p][n+1]*inv(a[p][p])%Mod);
}
return 0;
}