今天是图论专题
拓扑排序可以拿来求自环(我是伞兵
Dj的入队操作要放在if(dis[v]>dis[now]+tr[i].val)里面
T1:
样例输入:
5 13 2 3 4 0 0 12 4 5 20 0 0 40 0 0
样例输出:
81
题解:
法1:
这是一道简单的二叉树应用问题,问题中的结点数并不多,数据规模也不大,采用邻接矩阵存储,用Floyed算法(上网查阅)求出任意两结点之间的最短路径,然后穷举医院可能建立的n个结点位置,找出一个最小距离的位置即可。当然也可以用双链表结构或带父结点信息的数组存储结构来解决,但实际操作稍微麻烦了一点。
法2:
这是一个朴素得不能再朴素的方法,由于节点只有100个,我们反手枚举每个点作为医院的情况跑一遍DFS,求最小的那一个,完事!
#include<cstdio>
using namespace std;
struct Node{
int v,nxt;
}tr[10010];
int Head[10010];
int kok;
int n;
int val[10010];
int x,y;
int minn,ans;
inline void add(int u,int v)
{
kok++;
tr[kok].v=v;
tr[kok].nxt=Head[u];
Head[u]=kok;
}
inline void dfs(int now,int fath,int depth)
{
ans+=depth*val[now];
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(v==fath)continue;
dfs(v,now,depth+1);
}
}
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&val[i]);
scanf("%d%d",&x,&y);
if(x!=0){
add(x,i);
add(i,x);
}
if(y!=0){
add(y,i);
add(i,y);
}
}
minn=99999999;
for(int i=1;i<=n;i++){
ans=0;
dfs(i,0,0);
if(minn>ans)minn=ans;
}
printf("%d",minn);
}
/*
7
1 2 3
1 4 5
1 6 7
1 0 0
60 0 0
1 0 0
1 0 0
*/
T2:
样例输入:
4 6 1 2 1 1 3 2 1 4 3 2 3 1 2 4 2 3 4 1
样例输出:
6
题解:
考的时候看到这道题直接傻眼,稍微思考了一下果断放弃,但是事实上,这道题远远没有想象的那么困难。简化了题意就是,求有多少建图情况使得1到每个点的最小值不变(这有简化吗?)
进一步简化就是,先dj跑一边每个点的最小路径然后计算:
对于一个点有多少个相连的 满足:
edge(i,j)表示i,j之间的边长。而最终答案就是每个点的个数相乘(排列组合的乘法原理,这些j更换后对dis没有影响,借此便可以算出有几种方案)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
struct Node{
int v,nxt;
int val;
}tr[1000010];
int Head[1000010];
int kok;
inline void add(int u,int v,int w)
{
kok++;
tr[kok].v=v;
tr[kok].val=w;
tr[kok].nxt=Head[u];
Head[u]=kok;
}
int n,m;
int dis[1010];
long long cnt[1010];
bool vis[1010];
long long ans;
priority_queue< pair<int , int> >q;
inline void djs()
{
memset(dis,0x7f,sizeof(dis));
memset(vis,false,sizeof(vis));
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty()){
int now=q.top().second;q.pop();
if(vis[now]==true)continue;
vis[now]=true;
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(dis[v]>dis[now]+tr[i].val){
dis[v]=dis[now]+tr[i].val;
cnt[v]=1ll;
q.push(make_pair(-dis[v],v));
}else if(dis[v]==dis[now]+tr[i].val)
cnt[v]+=1ll;
}
}
}
int main(void)
{
scanf("%d%d",&n,&m);
int u,v,w;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
djs();
ans=1ll;
long long Mod=(long long)(1ll<<31ll)-1ll;
cnt[1]=1ll;
for(int i=1;i<=n;i++){
ans=1ll*(ans*cnt[i])%Mod;
}
printf("%lld",ans);
}
T3:
样例输入:
4 5 1 2 3 1 4 5 2 4 7 2 3 6 3 4 8
样例输出:
3 6
题解:
这题其实就是求图里面的最小生成树,就可以求出答案,使道路中分值最大值尽量小
证明:
求最小生成树一定会将每条边以边权排个序,依次选合法的边建立就像下图:
如果存在一条边使得最大的边权更小,假如是下图中的红色虚线
如果能使得答案更优,那它肯定比生成树中的最大边小,但是求出的生成树已经是最小的了,所以这样的边一定会被最小生成树给覆盖,或者大于已被选的边(下图蓝边)
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{
int u,v,val;
}tr[90010];
int n,m;
int fa[3000],size[3000];
int cnt,maxx=0;
inline int find(int x)
{
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
inline bool query(int x,int y)
{
x=find(x),y=find(y);
if(x==y)return false;
if(size[x]>size[y]){
size[x]+=size[y];
fa[y]=x;
}else{
size[y]+=size[x];
fa[x]=y;
}
return true;
}
inline bool tmp(Node o1,Node o2)
{
return o1.val<o2.val;
}
int main(void)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)size[i]=1,fa[i]=i;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&tr[i].u,&tr[i].v,&tr[i].val);
}
sort(tr+1,tr+1+m,tmp);
for(int i=1;i<=m;i++){
if(cnt==n-1)break;
if(query(tr[i].u,tr[i].v)==true){
maxx=max(maxx,tr[i].val);
cnt++;
}
}
printf("%d %d",cnt,maxx);
}
T4:
样例输入:
2 3 1 2 2 1 3 3 4 1 2 3 2 3 4 3 4 5
样例输出:
4 17
题解:
由于最后只剩下15分钟打这道题,所以读题时理解出了问题,是要求两点新建的边要比两点之间的所有路径要大,而不是大于所有与两点相连的边
然后这又是一道最小生成树,是的你没听错,严格来说也不算是,毕竟他给的就是一棵树,所以我们把所有边排个序,依次加边。对于每加一条边,这条边edge(i,j)会将两个连通块相接,那么对于任何任何一对处在处在不同连通块中的两点,都可以建一条大于edge(i,j)的边对答案贡献。而能建立的边的个数就是两个连通块的大小-1。这一系列加边和求连通块的操作就可以用并查集来实现
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{
int u,v;
long long w;
}tr[6010];
int T;
int n;
int fa[6010];
long long size[6010];
int x,y;
long long ans;
inline bool tmp(Node xx,Node yy){return xx.w<yy.w;}
inline int find(int t)
{
if(fa[t]==t)return t;
return fa[t]=find(fa[t]);
}
int main(void)
{
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<n;i++)scanf("%d%d%lld",&tr[i].u,&tr[i].v,&tr[i].w);
for(int i=1;i<=n;i++)fa[i]=i,size[i]=1;
sort(tr+1,tr+n,tmp);
ans=0ll;
for(int i=1;i<n;i++){
x=find(tr[i].u);
y=find(tr[i].v);
if(x==y)continue;
ans+=1ll*(tr[i].w+1ll)*(size[x]*size[y]-1ll);
fa[y]=x;
size[x]+=size[y];
}
printf("%lld\n",ans);
}
}
T5:
样例输入:
5 5 4 3 5 6 1 1 2 1 1 4 1 2 3 2 3 5 1 4 5 2
样例输出:
5
题解:
这是一道做过的板子题,但是没有做对需要反思。
当时做的时候认为是直接跑一遍树形DP,记录1到 i 的最大值和最小值,但这样要出问题,因为一是会出现无法达到的情况,而是单项会使答案不是最优的,问题出在卖出价的求法(即maxx数组出了问题
正解就是,建两个图,一个反向,一个正向,跑两遍spfa求出F和D数组
D[i]表示从1到i一路上最小值
F[i]表示从i到n的最大值
答案就是
特别注意建两个图的时候,tot和kok,Head_1和Head_2别搞混了,别问我为什么晓得
#include<queue>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#define maxn 100010
using namespace std;
struct Node{
int v,nxt;
}tr[maxn*2],ntr[maxn*2];
int Head_1[maxn];
int Head_2[maxn];
int tot=0,kok;
int n,m;
int val[maxn];
int D[maxn],F[maxn];
bool vis[maxn];
queue<int> q;
inline void Add(int u,int v)
{
tot++;
tr[tot].v=v;
tr[tot].nxt=Head_1[u];
Head_1[u]=tot;
}
inline void ppV(int u,int v)
{
kok++;
ntr[kok].v=v;
ntr[kok].nxt=Head_2[u];
Head_2[u]=kok;
}
inline void djst_min()
{
memset(D,0x7f,sizeof(D));
memset(vis,false,sizeof(vis));
D[1]=val[1];
q.push(1);
while(!q.empty()){
int now=q.front();q.pop();
vis[now]=true;
for(int i=Head_1[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(D[v]>min(D[now],val[v]))D[v]=min(D[now],val[v]);
if(vis[v]==false)
q.push(v);
}
}
}
inline void djst_max()
{
memset(F,-0x7f,sizeof(F));
memset(vis,false,sizeof(vis));
F[n]=val[n];
q.push(n);
while(!q.empty()){
int now=q.front();q.pop();
vis[now]=true;
for(int i=Head_2[now];i;i=ntr[i].nxt){
int v=ntr[i].v;
F[v]=val[v];
if(F[v]<max(F[now],val[v]))F[v]=max(F[now],val[v]);
if(vis[v]==false)
q.push(v);
}
}
}
int main(void)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
if(w==1){
Add(u,v);
ppV(v,u);
}
else{
Add(u,v);Add(v,u);
ppV(v,u);ppV(u,v);
}
}
djst_max();
djst_min();
int ans=-0x7ffffff;
for(int i=1;i<=n;i++)ans=max(ans,F[i]-D[i]);
printf("%d",ans);
}
T6:
样例输入:
2 1 1 2
样例输出:
201
题解:
一开始以为是单向建图后直接跑答案,再特判一下是否会有环完事。然后就有惊无险的WA了两个点。事实上这道题需要拓扑排序,想想确实,每次更新入度为零的节点和本题吻合
注意1:拓扑排序可以拿来求自环(我是伞兵
注意2:dj和spfa的入队操作要放在if(dis[v]>dis[now]+tr[i].val)里面
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
struct Node{
int v,nxt;
}tr[40010];
int Head[40010];
int kok;
int n,m,k;
int ans;
int x,y;
int root[10010],cnt;
bool vis[10010];
int srk[10010];
int val[10010];
int rit[10010];
queue<int>q;
inline void add(int u,int v)
{
kok++;
tr[kok].v=v;
tr[kok].nxt=Head[u];
Head[u]=kok;
}
inline void BFS()
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++){
if(rit[i]==0){
q.push(i);
val[i]=100;
}
}
k=0;
while(!q.empty()){
int now=q.front();q.pop();
if(vis[now]==true)continue;
vis[now]=true;
srk[now]+=1;
k++;
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
val[v]=max(val[v],val[now]+1);
rit[v]--;
if(rit[v]==0)q.push(v);
}
}
}
int main(void)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add(y,x);
rit[x]++;
}
cnt=0;
BFS();
ans=0;
if(k!=n){
printf("Poor Xed");
return 0;
}
for(int i=1;i<=n;i++)ans+=val[i];
printf("%d",ans);
}
T7:
样例输入:
ABCDE
样例输出:
Yes
题解:
由于这道题好心的使用顺序结构输入,就让这道题简单不少,的两个儿子编号一定为和
,判断节点
的两个儿子是否都为’#‘或者都不为’#‘,真就继续,为假就直接输出No结束。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
int son[100000],len,j;
char s[1000000];
int main(void)
{
cin>>s;
len=(int)strlen(s);
for(int i=1;i<len;i++){
if(s[i]!='#'){
if(s[(i+1)/2-1]!='#')
son[(i+1)/2-1]++;
else{
j=(i+1)/2-1;
while(s[j]=='#')j++;
son[j]++;
}
}
}
for(int i=0;i<len;i++){
if(son[i]%2==1){
printf("No");
return 0;
}
}
printf("Yes");
return 0;
}
T8:
样例输入:
4 2
样例输出:
12
题解:
先人脑跑了一遍发现,可以从n-1推到n的情况,当最深层被填满的时候会发现除了最后一层,其它节点都会变回操作之前的样子,相当于上一层的落到左儿子,然后另一半落到右儿子
得到:
n=1 n=2 n=3 n=4
f[1]=1 f[1]=2 f[1]=4 f[1]=8
f[2]=3 f[2]=6 f[2]=12
f[3]=5 f[3]=10
f[4]=7 f[4]=14
f[5]=9
f[6]=13
f[7]=11
f[8]=15
规律出现了
#include<cstdio>
using namespace std;
int f[600000];
int n,I,len;
int main(void)
{
scanf("%d%d",&n,&I);
if(n==1){
printf("1");
return 0;
}
f[1]=2;f[2]=3;len=2;
for(int k=3;k<=n;k++){
for(int i=1;i<=len;i++){
f[i]*=2;
}
for(int i=len+1;i<=len*2;i++){
f[i]=f[i-len]+1;
}
len*=2;
}
printf("%d",f[I]);
}
总结:
今天的测试发现了一些知识误区和知识漏洞,有收获,明天加油