[COCI 2009/2010 – Constest #7 SVEMIR]10^5个点的完全图的最小生成树、堆、并差集、暴力解法+求正解~

【题目地址】http://evaluator.hsin.hr/login.php?redirect=index.php
【题目大意】
给定N<=10^5个点,每个点有一个三维坐标(Xi,Yi,Zi)
两个点间的距离定义为Distance=max(|Xi-Xj|,|Yi-Yj|,|Zi-Zj|)。
然后让你求它的最小生成树。
【算法分析】
标题是虽然是事实,但是明显是吓人的。。。。(为啥我也变成标题党了= =)
然后这题我的做法非常暴力
我的方法如下(由于难以言表,请尽量结合程序围观。。。):
首先必然的一件事是:用的kruskal  (MS拼错了,谁来纠正一下。。。)
先把点分开三个,分到不同的数组里,分别按x,y,z排序。
然后每个点设置一个已走步长:Lena[i] Lenb[i] Lenc[i]。
再建3个堆。
分别装的key值是类似这种a[i+Lena[i]].x-a[i].x。
然后每次取a,b,c三个堆中key值最小的出来,然后kruskal。
然后再把Lena[i]+1,再次把a[i+Lena[i]].x-a[i].x放入堆中。
(因为这个数肯定是增大了,所以能保证先把小的边弄了。)
最后并差集弄一下,就是一个完整的超暴力kruskal。
【其他】
Test # Score Time Memory Result 1 10 0.00s 9760 kB Correct! 2 10 0.00s 9760 kB Correct! 3 10 0.00s 9760 kB Correct! 4 10 0.00s 9760 kB Correct! 5 10 0.00s 9760 kB Correct! 6 10 0.04s 9920 kB Correct! 7 10 0.31s 9760 kB Correct! 8 10 0.30s 9760 kB Correct! 9 10 0.47s 9760 kB Correct! 10 10 0.80s 9760 kB Correct!
期待众神牛讲出正解。毕竟做人不能那么暴力。
【CODE】
#include #include #include const int N=100005;

struct Heap_Type{
int tot;
struct Node{int pos,key;}d[N],tmp;

void swap(Node &x,Node &y){tmp=x; x=y; y=tmp;}

void up(int k){
while (k>1){
if (d[k].key swap(d[k],d[k/2]);
k/=2;
}else break;
}
}

void down(int k){
int p;
while (k*2<=tot){
p=k*2;
if (p+1<=tot && d[p+1].key if (d[p].key swap(d[p],d[k]);
k=p;
}else break;
}
}

void ins(int key,int pos){
tot++;
d[tot].key=key;
d[tot].pos=pos;
up(tot);
}

void del(){
swap(d[1],d[tot]);
tot–;
down(1);
}
}Heapa,Heapb,Heapc;
struct Point{int x,y,z,pos;}a[N],b[N],c[N];
int n,times;
int F[N],Num[N],Lena[N],Lenb[N],Lenc[N];
long long ans;

int cmpa(const void *x,const void *y){return ((Point*)x)->x-((Point*)y)->x;}
int cmpb(const void *x,const void *y){return ((Point*)x)->y-((Point*)y)->y;}
int cmpc(const void *x,const void *y){return ((Point*)x)->z-((Point*)y)->z;}

void init(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
a[i].pos=i;
c[i]=b[i]=a[i];
Lena[i]=Lenb[i]=Lenc[i]=1;
F[i]=i; Num[i]=1;
}
qsort(a+1,n,sizeof(Point),cmpa);
qsort(b+1,n,sizeof(Point),cmpb);
qsort(c+1,n,sizeof(Point),cmpc);
Heapa.tot=Heapb.tot=Heapc.tot=0;
for (int i=1;i Heapa.ins(a[i+1].x-a[i].x,i);
Heapb.ins(b[i+1].y-b[i].y,i);
Heapc.ins(c[i+1].z-c[i].z,i);
}
}

int GF(int p){
int res=p,tmp;
while (F[res]!=res) res=F[res];
while (p!=res){
tmp=F[p];
F[p]=res;
p=tmp;
}
return res;
}

inline bool set_union(int x,int y){
if (GF(x)==GF(y)) return false;
times++;
if (Num[F[x]] Num[F[y]]+=Num[F[x]];
F[F[x]]=F[y];
}else{
Num[F[x]]+=Num[F[y]];
F[F[y]]=F[x];
}
return true;
}

void solve(){
times=1; ans=0;
int i;
while (times if (Heapa.d[1].key<=Heapb.d[1].key && Heapa.d[1].key<=Heapc.d[1].key){
i=Heapa.d[1].pos;
if (set_union(a[i].pos,a[i+Lena[i]].pos)) ans+=Heapa.d[1].key;
Lena[i]++;
Heapa.del();
if (i+Lena[i]<=n) Heapa.ins(a[i+Lena[i]].x-a[i].x,i);
}else
if (Heapb.d[1].key<=Heapa.d[1].key && Heapb.d[1].key<=Heapc.d[1].key){
i=Heapb.d[1].pos;
if (set_union(b[i].pos,b[i+Lenb[i]].pos)) ans+=Heapb.d[1].key;
Lenb[i]++;
Heapb.del();
if (i+Lenb[i]<=n) Heapb.ins(b[i+Lenb[i]].y-b[i].y,i);
}
else{
i=Heapc.d[1].pos;
if (set_union(c[i].pos,c[i+Lenc[i]].pos)) ans+=Heapc.d[1].key;
Lenc[i]++;
Heapc.del();
if (i+Lenc[i]<=n) Heapc.ins(c[i+Lenc[i]].z-c[i].z,i);
}
}
printf("%lldn",ans);
}

int main(){
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
init();
solve();
}

[SPOJ412 COVER]特殊图的最小费用流巧妙优化

【题目地址】https://www.spoj.pl/problems/COVER/
【题目大意】
有n个点,m条有向边,这m条有向边的权值w=cost[x]+cost[y],求k条边满足:
1、这k条边之间没有共起点和共终点的
2、这k条边的权和最小
【算法分析】
容易建图:
将n个点每个点拆成(i,i’)
然后对于有向边(x,y),连边(x,y’),该边权为cost[x]+cost[y]
于是相当于对这个二分图,求k次最小费用流的增广。
然后由于m太大,我们需要更快的算法。
对于观察每次增广,实际上就是匈牙利算法中的找交错轨,
并且新增的权总为增广路中的cost[起点]+cost[终点]
然后我们可以利用这个性质,将cost排序,然后有:当终点一样的情况下,让前面的先匹配比较好。
所以一个点只用进去一次就可以了。
最后就是每次枚举从某一点出发找增广路,找出本次增广增加的费用最小的。
然后去实现这个增广即可。
。。。罗嗦了这么多,结合程序看吧= =
【其它】
时间复杂度O(k*(n+m))
【CODE】
#include #include #include const int N=10005;
const int INF=1000000000;
struct gtp{int y,next;}g[1001111];
int n,m,k,ans,times;
int ls[N],link[N],v[N],cost[N][2],zz[N],To_y[N],cx[N];

void Read(int &x){
char ch=getchar();
while (ch<'0' || ch>'9') ch=getchar();
for (x=0;ch>=’0′ && ch<='9';ch=getchar())
x=x*10+ch-‘0’;
}

int cmp(const void *x,const void *y){return ((int*)x)[0]-((int*)y)[0];}

void init(){
memset(ls,0,sizeof(ls));
memset(link,0,sizeof(link));
memset(cx,0,sizeof(cx));
Read(k); Read(n); Read(m);
for (int i=1;i<=n;i++){
Read(cost[i][0]);
cost[i][1]=i;
}
qsort(&cost[1][0],n,sizeof(int)*2,cmp);
for (int i=1;i<=n;i++) zz[cost[i][1]]=i;
for (int i=1,x,y;i<=m;i++){
Read(x); Read(y);
x=zz[x]; y=zz[y];
g[i].y=y; g[i].next=ls[x]; ls[x]=i;
}
}

int Try(int p){
int res=INF,tmp;
for (int t=ls[p];t;t=g[t].next)
if (!v[g[t].y]){
v[g[t].y]=1;
if (!link[g[t].y]) tmp=cost[g[t].y][0];
else tmp=Try(link[g[t].y]);
if (tmp res=tmp;
To_y[p]=g[t].y;
}
}
return res;
}

void expand(int p){
int q=link[To_y[p]];
link[To_y[p]]=p;
if (q) expand(q);
}

void solve(){
ans=0;
for (int i=1;i<=k;i++){
memset(v+1,0,sizeof(int)*n);
memset(To_y+1,0,sizeof(int)*n);
int now=INF,nowi,tmp;
for (times=1;times<=n;times++)
if (cx[times]==0){
tmp=cost[times][0]+Try(times);
if (tmp now=tmp;
nowi=times;
}
}
if (now==INF){
printf("NONEn");
return;
}
ans+=now;
expand(nowi);
cx[nowi]=1;
}
printf("%dn",ans);
}

int main(){
int Tc;
Read(Tc);
for (int i=1;i<=Tc;i++){
init();
solve();
}
}

[SGU206 Roads]KM算法的妙用

【题目大意】

给定N个城市和M条道路,其中前N-1条道路是大路,其它都是小路,并保证大 路可构成N个城市 的生成树。

第i条路费用为ci,要求你谎报费用(设第i条路上报的费用为di),使得对费用数组d来说,N-1条大路恰为最小生成树,并且sum|ci-di|尽可能小。

(摘自:http://crfish.blogbus.com/logs/62846336.html

【算法分析】

本题的解题报告来自:http://wenku.baidu.com/view/7bfaa802de80d4d8d15a4faf.html

大概是这样:

令w[i]表示第i条路需要改变的量,且w[i]>=0

假设该路是大路,显然d[i]=c[i]-w[i];

假设该路是小路,显然d[i]=c[i]+w[i];

然后对于树环有限制条件:c[i]-w[i]<=c[j]+w[j]  (其中i为大路,j为小路)

变形成:w[i]+w[j]>=c[i]-c[j]。

于是KM里有不等式w[i]+w[j]>=P[i,j]。

然后由于最后KM的顶标之和等于最佳匹配的总权值,而对于该题而言,这个总权又是必须的。

于是两个问题等价了。

【CODE】

#include #include #include struct gtp{int x,y,w,op,next;}g[810];
int n,m,p,dis;
int w[405][405],fa[66],depth[66],lx[405],ly[405],link[405],ls[66];
bool cx[405],cy[405];

inline void swap(int &x,int &y){x=x^y; y=x^y; x=x^y;}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d%d%d",&g[i].x,&g[i].y,&g[i].w);
g[i].next=ls[g[i].x];
ls[g[i].x]=i;
g[i].op=i+m;
}
for (int i=m+1;i<=2*m;i++){
g[i].x=g[i-m].y;
g[i].y=g[i-m].x;
g[i].w=g[i-m].w;
g[i].next=ls[g[i].x];
ls[g[i].x]=i;
g[i].op=i-m;
}
}

void buildtree(int p){
for (int t=ls[p];t;t=g[t].next)
if ((t fa[g[t].y]=t;
depth[g[t].y]=depth[p]+1;
buildtree(g[t].y);
}
}

void buildgraph(){
memset(w,0,sizeof(w));
int i,x,y,Fa;
for (i=n;i<=m;i++){
x=g[i].x; y=g[i].y;
while (y!=x){
if (depth[x]>depth[y]) swap(x,y);
Fa=fa[y];
if (Fa>=n) Fa=g[Fa].op;
w[Fa][i-n+1]=max(0,g[Fa].w-g[i].w);
y=g[fa[y]].x;
}
}
p=max(n-1,m-n+1);
}

bool find(int i){
cx[i]=true;
int q;
for (int j=1;j<=p;j++)
if (lx[i]+ly[j]==w[i][j] && !cy[j]){
cy[j]=true;
q=link[j];
link[j]=i;
if (!q || find(q)) return true;
link[j]=q;
}
else if (!cy[j]) dis=min(dis,lx[i]+ly[j]-w[i][j]);
return false;
}

void KM(){
int i,j;
for (i=1;i<=p;i++)
for (j=1;j<=p;j++)
if (w[i][j]>lx[i])
lx[i]=w[i][j];
for (i=1;i<=p;i++){
for (;;){
for (j=1;j<=p;j++) cx[j]=false;
for (j=1;j<=p;j++) cy[j]=false;
dis=0x7FFFFFFF;
if (find(i)) break;
for (j=1;j<=p;j++) if (cx[j]) lx[j]-=dis;
for (j=1;j<=p;j++) if (cy[j]) ly[j]+=dis;
}
}
}

void output(){
for (int i=1;i<=p;i++) g[i].w-=lx[i];
for (int i=1;i<=p;i++) g[i+n-1].w+=ly[i];
for (int i=1;i<=m;i++)
printf("%dn",g[i].w);
}

int main(){
input();
buildtree(1);
buildgraph();
KM();
output();
}

[SCOI2010 第一试 游戏]二分图匹配 or 并差集

【题目】

Description

lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示。当他使用某种装备 时,他只能使用该装备的某一个属性。并且每种装备最多只能使用一次。
游戏进行到最后,lxhgww遇到了终极boss,这个终极boss很奇怪,攻击他的装备所使用的属性值必须从1开始连续递增地攻击,才能对boss产生 伤害。也就是说一开始的时候,lxhgww只能使用某个属性值为1的装备攻击boss,然后只能使用某个属性值为2的装备攻击boss,然后只能使用某个 属性值为3的装备攻击boss……以此类推。
现在lxhgww想知道他最多能连续攻击boss多少次?

Input

输入的第一行是一个整数N,表示lxhgww拥有N种装备
接下来N行,是对这N种装备的描述,每行2个数字,表示第i种装备的2个属性值

Output

输出一行,包括1个数字,表示lxhgww最多能连续攻击的次数。

Sample Input

3
1 2
3 2
4 5

Sample Output

2

Hint

对于30%的数据,保证N<=1000
对于100%的数据,保证N<=1000000 【算法分析】
做法1:
这是通法~
我们把那个10000个数字放在二分图的左边,然后把武器放在二分图的右边。
对于一个武器,连左边两个对应的点。
然后从左边的点1开始一直像正常的匹配那样搞下去,直到某一个地方,找不到增广路,证明前i个点不能被同时满足了。那么输出i-1即可。

做法2:
这个我觉得并不容易想到。
把那10000个点看成点。
然后武器看成无向边。
(1)对于一个联通块,假如不含环(就是一棵树),那么必定可以满足其中任意的p-1个点。
(2)对于一个联通块,假如含环,那么必定全部的p个点都能满足。
如此用并差集维护即可。
对(1)容易用数学归纳法证明。
对(2),可以通过(1)来证明。
【其他】
实际测试表明,匹配和并差集的速度差不多。
注:并差集用的IO外挂,所以速度会快很多,实际上去掉以后速度是差不多的。
【CODE——匹配】
#include #include #include const int N=1000005;
struct gtp{int y,next;}g[N*2];
int n,e;
int a[N*2][2],v[N*2],ls[10005],link[N*2];

inline void addedge(int x,int y){
e++;
g[e].y=y; g[e].next=ls[x]; ls[x]=e;
}

bool find(int p,int kk){
int q;
for (int t=ls[p];t;t=g[t].next)
if (v[g[t].y]!=kk){
v[g[t].y]=kk;
q=link[g[t].y];
link[g[t].y]=p;
if (!q || find(q,kk)) return true;
link[g[t].y]=q;
}
return false;
}

void solve(){
e=0; memset(ls,0,sizeof(ls));
for (int i=1;i<=n;i++){
addedge(a[i][0],i);
addedge(a[i][1],i);
}
for (int i=1;i<=10001;i++){
if (!find(i,i)){
printf("%dn",i-1);
return;
}
}
}

int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d%d",&a[i][0],&a[i][1]);
solve();
}

【CODE——并差集】
#include #include #include const int N=10005;
int n;
int f[N],v[N];
char ch;

int gf(int p){
if (f[p]==p) return p;
return f[p]=gf(f[p]);
}

inline void Read(int &x){
x=0;
ch=getchar();
while (ch==’ ‘ || ch==’n’) ch=getchar();
while (ch!=’ ‘ && ch!=’n’){
x=x*10+ch-‘0’;
ch=getchar();
}
}

int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
int x,y,i,tmp;
for (i=1;i<=N;i++){
f[i]=i;
v[i]=0;
}
Read(n);
for (i=1;i<=n;i++){
Read(x); Read(y);
if (gf(x)!=gf(y)){
if (f[x]>f[y]){tmp=x; x=y; y=tmp;}
v[f[y]]|=v[f[x]];
f[f[x]]=f[y];
}else v[f[x]]=1;
}
for (i=1;i if (f[i]==i && !v[i]){
printf("%dn",i-1);
break;
}
}

[NOI2007 day1 社交网络]floyed变形

【题目】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1491

【算法分析】

就是利用floyed求最短路。然后加多一个数组表示num[i][j]表示从i到j的最短路的数目。

然后在松弛的地方因为乘法原理,应当变成num[i][k]*num[k][j],相等的话要加上方案。

最后统计一下就好了。

【其它】1A

【CODE】

#include using namespace std;
int n,m;
int d[122][122];
long long num[122][122];

void input(){
    memset(d,50,sizeof(d));
    memset(num,0,sizeof(num));
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        d[x][y]=w;
        d[y][x]=w;
        num[x][y]=1;
        num[y][x]=1;
    }   
}   

void floyed(){
    int i,j,k;
    for (k=1;k<=n;k++)
      for (i=1;i<=n;i++)
        for (j=1;j<=n;j++)
          if (i!=j && j!=k && i!=k){
              if (d[i][k]+d[k][j]                  d[i][j]=d[i][k]+d[k][j];
                  num[i][j]=num[i][k]*num[k][j];
              }   
              else if (d[i][k]+d[k][j]==d[i][j])
                  num[i][j]+=num[i][k]*num[k][j];
          }   
}   

void output(){
    int i,j,k;
    for (k=1;k<=n;k++){
        double ans=0;
        for (i=1;i<=n;i++)
          for (j=1;j<=n;j++)
            if (i!=j && j!=k && i!=k && d[i][j]==d[i][k]+d[k][j])
                ans+=num[i][k]*num[k][j]*1.0/num[i][j];
        printf("%.3lfn",ans);
    }   
}   

int main(){
    freopen("network.in","r",stdin);
    freopen("network.out","w",stdout);
    input();
    floyed();
    output();
}