The Summer Training Summary-- the second
- 拓扑排序 hdu 1285
问题分析 怎么说呢 拓扑排序 给我的感觉就是 一棵树 从树冠 往下
每次遇到 比他的小的 就把它的入度+1 就往树下 走 然后从树冠 找
入度为0 是就是一个 小树冠 其下拥有若干 小树 小分支的入度为0 意味着到了
小树冠的底部但着并不意味着 这就是最小值 因为 可能还有比这个数的最大入度
还大的数存在
为了满足 不确定的 名次按照 队伍字典序排序
借助于优先队列来实现这个; 首先将 起始入度为0 的队伍加入 这意味着 树冠 (他们的 名词相对来说是靠前的单不能确定他们的具体名词 优先队列巴拉巴拉...)
然后 出队 扫描 分支 入度减一 存在 入度为0 就继续加入优先队列
具体代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
int n,m,map[505][505],in[505];
int main()
{
while(cin>>n>>m)
{
mem(map);
mem(in);
while(m--)
{
int a,b;
cin>>a>>b;
if(!map[a][b])
{
map[a][b]=1;
in[b]++;
}
}
priority_queue<int,vector<int>,greater<int> > q;
for(int i=1;i<=n;i++)
{
if(!in[i])
q.push(i);
}
bool f=0;
while(q.size())
{
int top=q.top();
q.pop();
in[top]--;
if(!f)
cout<<top;
else
cout<<" "<<top;
f=1;
for(int i=1;i<=n;i++)
{
if(map[top][i])
{
in[i]--;
if(!in[i])
q.push(i);
}
}
}
cout<<endl;
}
return 0;
}
H - 拓扑排序 (2) hdu 3342
问题分析 :
和上个 拓扑排序一样只不过是是反向查询 是否是存在循环结构 如果 存在就意味这有错误
而这在 程序中反映出来就是 查询值等于n;
具体代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
int n,m,map[505][505],in[505];
int main()
{
while(cin>>n>>m&&n&&m)
{ mem(map);
mem(in);
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
if(!map[a][b])
{
map[a][b]=1;
in[b]++;
}
}
int flag=0;
int i,j;
for( i=0;i<n;i++)
{
for( j=0;j<n;j++)
{
if(!in[j])
{
in[j]--;
for(int k=0;k<n;k++)
{
if(map[j][k])
in[k]--;
}
break;
}
}
if(j==n)
{
flag=1;
break;
}
}
if(flag)
cout<<"NO
";
else
cout<<"YES
";
}
return 0;
}
L - 拓扑排序 HDU 2647
就是拓扑排序 中间 加了一个 一888作为基值
代码如下:
#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
int main()
{
int n,m;
while(cin>>n>>m)
{
vector<int> map[20001];
int in[10001],mo[10001];
for(int i=1;i<=n;i++)
map[i].clear();
mem(in);
queue<int> q;
while(m--)
{
int a,b;
cin>>a>>b;
map[b].push_back(a);
in[a]++;
}
for(int i=1;i<=n;i++)
if(!in[i])
{
q.push(i);
mo[i]=888;
}
int tot=0;
int sum=0;
while(!q.empty())
{
int x=q.front();
sum+=mo[x];
tot++;
q.pop();
for(int i=0;i<map[x].size();i++)
{
in[map[x][i]]--;
mo[map[x][i]]=mo[x]+1;
if(!in[map[x][i]])
q.push(map[x][i]);
}
}
if(tot<n)
cout<<-1<<endl;
else
cout<<sum<<endl;
}
return 0;
}
I - 并查集 poj 1456
问题分析:
这题有多种解法 我用是结构体 加优先队列 (结构体可以换成pair)
建立 天数 与 价格的结构体
然后按照 价格进行排序
开一个 cnt记录 所在天数(每天限制卖1中水果)
从头扫到尾部 在这期间
因为可能存在同一天但是确有多种选择
保持cnt<=结构体的.day
如果大于了就 检查 队首是否小于 新的 小就入队
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
struct node
{
int vla;
int d;
}ans[10005];
int cmp(node a,node b)
{
return a.d<b.d;
}
int main()
{
int n;
while(cin>>n)
{
for(int i=0;i<n;i++)
cin>>ans[i].vla>>ans[i].d;
sort(ans,ans+n,cmp);
long long sum=0;
int cnt=1;
priority_queue<int,vector<int>,greater<int> >q;
for(int i=0;i<n;i++)
{
if(cnt<=ans[i].d)
{
q.push(ans[i].vla);
cnt++;
}
else if(q.top()<ans[i].vla)
{
q.pop();
q.push(ans[i].vla);
}
}
while(q.size())
{
sum+=q.top();
q.pop();
}
cout<<sum<<endl;
}
return 0;
}
J - 并查集 poj 1611
问题分析:并查集模板 直接套 然后求0点下的子节点个数
代码如下:
#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
int sum[30005],r[30005];
int find(int a)
{
if(r[a]!=a)
r[a]=find(r[a]);
return r[a];
}
int build(int a,int b)
{
int x=find(a);
int y=find(b);
if(x!=y)
{
r[y]=x;
sum[x]+=sum[y];
}
return 0;
}
int main()
{
int n,m;
while(cin>>n>>m&&n)
{
for(int i=0;i<n;i++)
{
sum[i]=1;
r[i]=i;
}
while(m--)
{
int t,a,b;
cin>>t>>a;
t--;
while(t--)
{
cin>>b;
build(a,b);
}
}
cout<<sum[find(0)]<<endl;
}
return 0;
}
M - 带权并查集 hdu 3038D
问题分析:
带权并查集 核心就是更新权值
主要 公式 寻根时的更新: val[a]+=val[pr[a]];
建立新链接是的更新 val[find(b)]=val[a]+v-val[b];
同根时检查 是否符合权值
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn =200005;
int val[maxn],pr[maxn];
int find(int a)
{
if(pr[a]==-1)
return a;
int t=find(pr[a]);
val[a]+=val[pr[a]];
return pr[a]=t;
}
int main()
{
int n,m,ans=0;
while(cin>>n>>m)
{
ans=0;
mem(val,0);
mem(pr,-1);
while(m--)
{
int a,b,v;
cin>>a>>b>>v;
a--;
int x=find(a);
int y=find(b);
if(x!=y)
{
pr[y]=x;
val[y]=val[a]-val[b]+v;
}
else if( val[b]-val[a]!=v)
ans++;
}
cout<<ans<<endl;
}
return 0;
}
N - 种类并查集 POJ 2492
.种类并查集有2中写法:
1:一种是开倍数数组记记录 检查是否冲突 不冲突就合并(双向)
2:还有一种 是依赖于一个 偏移向量数组(相当于 权值数组 大神起的名字一听就感觉霸气) 来区别种类
这个题 用了 第一种方法 并 配了 测试数据
感觉关键就是 a-b+maxn b-a+maxn建立关联但他们相对 性别来说又是相对独立的
一旦出现 相同性别sex的时候就会找到同一个根
测试输出 各个数字 含义标式:
起始值 -起始值根植-过程根植-尾根植(就是root[a]=a情况 值a/root[a])
1000 4 4
1 2
a-again 1 1000002 1000002 a-end
b-maxn|-again: 2 2 b-end
b-again 1000002 1000002 b-end
a-again 2 1000001 1000001 a-end
b-maxn|-again: 1 1000002 1000002 b-end
b-again 1000001 1000001 b-end
2 3
a-again 2 1000001 1000003 1000003 a-end
b-maxn|-again: 3 3 b-end
b-again 1000003 1000003 b-end
a-again 3 1000002 1000002 a-end
b-maxn|-again: 2 1000003 1000003 b-end
b-again 1000002 1000002 b-end
1 4
a-again 1 1000002 1000004 1000004 a-end
b-maxn|-again: 4 4 b-end
b-again 1000004 1000004 b-end
a-again 4 1000003 1000003 a-end
b-maxn|-again: 1 1000004 1000004 b-end
b-again 1000001 1000003 1000003 b-end
1 3
1000004 Scenario #1:
Suspicious bugs found!
最好是自己推理一遍,有点难以理解..我是看了好久(看自闭的呢种 0_0 .... )
输入 必须要用扫描 scanf() 要不就tle了...
代码如下:(含测试部分)
#include <iostream>
#include <cstring>
#include <algorithm>
#include<cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn =1000000;
int p[maxn*2],flag;
int find(int a)
{
if( p[a]!=a)
{
p[a]=find(p[a]);
cout<<p[a]<<" ";
}
return p[a];
}
int finds(int a)
{
if( p[a]!=a)
{
p[a]=find(p[a]);
}
return p[a];
}
int build(int a,int b)
{
int x,y;
x=finds(a);
y=finds(b-maxn);
if(x==y)
{
flag=0;
return 0;
}
y=finds(b);
if(x!=y)
{
p[x]=p[y];
}
/*
cout<<"a-again "<<a<<" "<<p[a]<<" ";
x=find(a);
cout<<" a-end"<<endl;
cout<<"b-maxn|-again: "<<b-maxn<<" "<<p[b-maxn]<<" ";
y=find(b-maxn);
cout<<" b-end"<<endl;
cout<<"b-again "<<b<<" "<<p[b]<<" ";
y=find(b);
cout<<" b-end"<<endl<<endl;
*/
}
int main()
{
int t,n,m,ans=0;
scanf("%d",&t);
for(int i=0;i<t;i++)
{
scanf("%d%d",&n,&m);
ans++;
for(int i=1;i<=n;i++)
{
p[i]=i;
p[i+maxn]=maxn+i;
}
flag=1;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
build(a,b+maxn);
build(b,a+maxn);
}
printf("Scenario #%d:
",ans);
if(flag)
printf("No suspicious bugs found!
");
else
printf("Suspicious bugs found!
");
cout<<endl;
}
return 0;
}
O - 种类并查集 POJ 1182
这个用就是 变量val[i] 定值 区分
这个 玩意推了好久才看明白
附上大神 全面解析
把大神的解析精简了一下面结合起来应该会更好!(这里用 val代替relation).
1.偏移数组 val[]值的含义
是由于 输入关系1 2决定(取用 输入的关系-1) 因此
规定 0:同类 1:子被吃 x->y 2: 子吃父x<-y (这样子就可以 用%3进行 压缩)
2.路径压缩算法 公式
x->y->z (子->父->爷)关系表达式(枚举推理,x->y代表 x是y 的根哈~)
val[爷]=(vla[子]+val[父])%3 (%3为了 防止大于等于3的情况出现 )
同时 可以得到 x->y(子被吃 1)=y<-x(子吃父3-2)
可以得到 x->y(子被吃 3-1)=y<-x(子吃父2)
3.集合间情关系 确定公式:
val[b]=(3- val[y]+(relation -1)+val[x])%3;(relation 缩写 r)
(b=find(y),b为y的根,输入:relation x y )
关系图 |
---|
X------>Y<-------B |
r-1 val[y] |
X------>Y------->B |
r-1 3-val[y] |
A----->-X------>Y------->B |
val[x] r-1 val[y] |
四. 判定公式
据题意共计4种为假话 |
---|
1. x>n或者y>n |
2. r=2&&x==y |
3.a!=b时 |
1) x==&&val[x]!=val[y] |
2) x==2&&(3-val[x]+val[y])%3!=1 |
关于3-2)的图解
目前状态 | 过程 | 所求状态 |
---|---|---|
A<---X---->Y | 变下方向:3-val[x] | A--->X---->Y |
累.......总算写完了哭泣~~
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include<cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn =50010;
int p[maxn],val[maxn];
int find(int a)
{
int t;
if(a==p[a])
return a;
t=p[a];
p[a]=find(t);
val[a]=(val[a]+val[t])%3;
return p[a];
}
int main()
{
int n,m,ans=0;
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
{
p[i]=i;
val[i]=0;
}
while(m--)
{
int relation,x,y;
scanf("%d%d%d",&relation,&x,&y);
if(x>n||y>n)
ans++;
else if(relation==2&&x==y)
ans++;
else
{
int a=find(x),b=find(y);
if(a!=b)
{
p[b]=a;
val[b]=(3+relation-1+val[x]-val[y])%3;
}
else
{
if(relation==1&&val[x]!=val[y])
ans++;
if(relation==2&&((3-val[x]+val[y])%3)!=1)
ans++;
}
}
}
printf("%d
",ans);
return 0;
}