高斯消元
写在前面
顾名思义,解多元一次方程组用的 \(O(n^3)\) 。
实在不行枚举一些未知数。
解决多个条件相互限制的情况,比如一些成环的dp。
模板
先上模板,这里用的整数的模板,浮点数的话应该更好写。
题目描述
给出一个线性方程组,有n个方程组,m个未知数。解这个线性方程组。测试数据保证如果有解,一定是整数解。
输入格式
第1行:2个整数n和m,(n, m <=15,且n不一定等于m)
接下来n行,每行m+1个整数,表示一个方程的m个未知数的系数和常数(绝对值不超过10)
输出格式
如果无解,输出“No solution”。
如果有唯一解,输出m行,每行一个未知数的值,保证解是整数,而且解的绝对值不超过10。格式见样例。
如果有无穷解,输出m行,如果未知数有确定解,直接输出。如果是自由变元,输出"not determined",格式见样例。
数据保证没有浮点数解。
样例
样例输入1
3 3
2 1 2 11
1 2 1 7
2 3 1 10
样例输出1
X[1] = 2
X[2] = 1
X[3] = 3
样例输入2
3 3
3 1 2 11
1 3 2 13
1 3 2 13
样例输出2
X[1] not determined
X[2] not determined
X[3] not determined
样例输入3
4 4
1 3 1 2 12
1 2 1 3 12
1 3 2 1 12
1 3 2 1 11
样例输出3
No solution
code
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,m,l;
int a[20][20],x[20];
bool v[20],flag=1;
void debug(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m+1;j++)
printf("%d ",a[i][j]);
printf("\n");
}
printf("--------\n");
}
void xy(int x,int y){
if(abs(a[x][l])<abs(a[y][l]))
swap(a[x],a[y]);
while(a[y][l]){
int k=a[x][l]/a[y][l];
for(int i=l;i<=m+1;i++)
a[x][i]-=a[y][i]*k;
if(abs(a[x][l])<abs(a[y][l]))
swap(a[x],a[y]);
}
}
void gs(){
l=1;
for(int i=1;i<=n&&l<=m;i++,l++){
int t=i;
for(int j=i+1;j<=n;j++)
if(abs(a[j][l])>abs(a[t][l]))
t=j;
swap(a[t],a[i]);
if(a[i][l]==0){
i--;
continue;
}
for(int j=i+1;j<=n;j++)
xy(i,j);
// debug();
}
for(int i=n;i>=1;i--){
int sum=0;
bool f=0;
for(int j=m;j>=1;j--)
if(a[i][j]){
f=1;
if(!v[j])
sum++;
else
a[i][m+1]-=x[j]*a[i][j];
}
if(!f&&a[i][m+1]){
flag=0;
return ;
}
if(sum==1)
for(int j=m;j>=1;j--)
if(a[i][j]&&!v[j]){
v[j]=1;
x[j]=a[i][m+1]/a[i][j];
break;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m+1;j++)
scanf("%d",&a[i][j]);
gs();
if(!flag){
printf("No solution");
return 0;
}
for(int i=1;i<=m;i++)
if(v[i])
printf("X[%d] = %d\n",i,x[i]);
else
printf("X[%d] not determined\n",i);
}
[HNOI2013] 游走
题目描述
给定一个 \(n\) 个点 \(m\) 条边的无向连通图,顶点从 \(1\) 编号到 \(n\),边从 \(1\) 编号到 \(m\)。
小 Z 在该图上进行随机游走,初始时小 Z 在 \(1\) 号顶点,每一步小 Z 以相等的概率随机选择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小 Z 到达 \(n\) 号顶点时游走结束,总分为所有获得的分数之和。 现在,请你对这 \(m\) 条边进行编号,使得小 Z 获得的总分的期望值最小。
输入格式
第一行是两个整数,分别表示该图的顶点数 \(n\) 和边数 \(m\)。
接下来 \(m\) 行每行两个整数 \(u,v\),表示顶点 \(u\) 与顶点 \(v\) 之间存在一条边。
输出格式
输出一行一个实数表示答案,保留三位小数。
样例 #1
样例输入 #1
3 3
2 3
1 2
1 3
样例输出 #1
3.333
数据规模
- 对于 \(30\%\) 的数据,保证 \(n\leq 10\)。
- 对于 \(100\%\) 的数据,保证 \(2\leq n \leq 500\), \(1 \leq m \leq 125000\),\(1 \leq u, v \leq n\),给出的图无重边和自环,且从 \(1\) 出发可以到达所有的节点。
题解
首先考虑每一条边,如果我们知道每一条边的期望经过次数,那么就贪心地期望次数大的边选较小的边权。
对于每一条有向边边 \((u,v)\) ,设它的期望经过次 \(g_i=\frac{f_u}{in_u}+\frac{f_v}{in_v}(u \neq n)\) 。\(f_i\) 为第 \(i\) 个点经过的期望,\(in_i\) 为度数。
考虑移项之后直接进行高斯消元。
code
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=505;
const int M=125000+5;
int n,m,cnt=1,in[N],head[N],l;
bool v[N];
double g[N][N],f[N],esp=1e-13,ans,w[M];
struct edge{
int u,v,next;
}e[M<<1];
void add(int u,int v){
cnt++;
e[cnt].u=u;
e[cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void ff(int x){
if(x==n){
f[x]=1;
return ;
}
for(int i=head[x];i;i=e[i].next){
int to=e[i].v;
if(to==n)
continue;
g[x][to]=1.0/in[to];
}
g[x][x]=-1;
if(x==1)
g[x][n]=-1;
}
void xy(double *x,double *y){
double k=y[l]/x[l];
for(int i=l;i<=n;i++)
y[i]-=x[i]*k;
}
void gs(){
l=1;
for(int i=1;i<n&&l<n;i++,l++){
int t=i;
for(int j=i;j<n;j++)
if(abs(g[j][l])-abs(g[t][l])>esp)
t=j;
swap(g[t],g[i]);
if(abs(g[i][l])<esp){
i--;
continue;
}
for(int j=i+1;j<n;j++)
xy(g[i],g[j]);
}
for(int i=n-1;i>=1;i--)
for(int j=n-1;j>=1;j--)
if(abs(g[i][j])>esp){
if(v[j])
g[i][n]-=g[i][j]*f[j];
else{
f[j]=g[i][n]/g[i][j];
v[j]=1;
break;
}
}
}
int main(){
int u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
in[u]++;in[v]++;
}
for(int i=1;i<=n;i++)
ff(i);
gs();
for(int i=2;i<=cnt;i+=2){
u=e[i].u;
v=e[i].v;
if(u!=n) w[i/2]+=f[u]/in[u];
if(v!=n) w[i/2]+=f[v]/in[v];
}
sort(w+1,w+1+m);
for(int i=1;i<=m;i++)
ans+=w[i]*(m+1-i);
printf("%.3lf",ans);
}