DAY_3(离散化,拓扑排序,前向星,最短路堆优化)

第三天。。有点晚,不过无伤大雅hhh

今天学了题目上这些东西,之前其实有的已经知道了,不过今天了解的更详细了点,话不多说,上知识点:

离散化

离散化是一个常用技巧,用于解决:1、数据范围太大,2、数据只和相对大小有关,而与具体值无关

实现:

 一、定义Node结构体,存入数组

struct Node
{
    int val;//原始值
    int order;//原始下标
}a[100001];

二、对数组a按val排序,此时,val和结构体新下表一一映射正好满足原来的相对大小关系

a_val:1 5 9 21 16 3
a.order:1 2 3 4 5 6
~~sort~~
a_val:1 3 5 9 16 21 
a_order:1 6 2 3 5 4

b:1 3 4 6 5 2
i:1 2 3 4 5 6

三、更新b数组

不去重:

for(int i=1;i<=n;i++)
b[a[i].order]=i;

去重:(若有重复元素,相等则继承上一个,否则加1)

for(int i=2,count=1;i<=n;i++)
{
    if(a[i].val==a[i-1].val)
    b[a[i].order]=count;
    else
    b[a[i].order]=++count;
}

拓扑排序

(在之前有提到的,直接CV)

拓扑排序:指的将一个有向无环图G所有顶点拍成一个线性序列,使有向无环图G的边集中的任意一条边<u,v>,始终满足u出现在v前面

首先,我们明确一下:有向无环图一定是拓扑序列(一定不能有环!!!)

无向图没有拓扑序列

对于拓扑序列,我们需要引入度——进入某点的路径数为入度,某点指向其他点的路径数为出度,总结一下,拓扑序列中只有从前指向后的边,没有从后指向前的边

对于一个有向无环图,一定有一个点的入度为0,如果找不到一个入度为0的点,这个图一定是带环的

对于拓扑排序,思路如下:

1、记录各个点的入度

2、找到入度为 0 的点并输出

3、找出所有以这个点发出的边,删除边,同时边的另一侧的点的入度 -1。

4、如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则不可以进行拓扑排序。

 邻接表vector存图

struct Edge
{
	int from;
	int to;
	int val;
};//定义边
const int N=1e5+5;
vector<edge> Map[N];
int main()
{
	for(int i=1;i<=n;i++)
	{
		cin>>a>>b;
		Map[a].push_back(b);//存图 
	}
	for(int k=0;k<Map[i].size;k++)//遍历边 
	{
		j=Map[i][k];
		indu[j]--;
		//后面是具体代码 
	}
}

前向星

链式前向星(邻接表的数组实现)

struct Edge
{
	int w,to;//w是权值,next指向下一个点,to指向到达的终点 
	int next;//next常用来做指针用 
}edge[2000005];
int cnt;
void add(int u,int v,int w)
{
	++cnt;
	edge[cnt].to=to;//终点 
	edge[cnt].w=w;//权值 
	edge[cnt].next=head[u];
	head[u]=cnt;
}
for(int i=1;i<=n;i++)
{
	cin>>u>>v>>w;
	add(u,v,w);//存图 
}

 图的遍历::

for(int i=1;i<=n;i++)
{//每个内循环访问第i个顶点出发的所有边 
	for(int k=head[i];k;k=edge[k].next)
	{
		//通过k可找到在edge中的对应边 
		//~~以下是具体程序~~
	}
}

最短路堆优化

用优先队列priority_queue维护dis的值升序排列(log量级)

struct heap
{
	int v,dis;
	bool operator<(const heap &p)const
	{
		return p.dis<dis;
	}
};
priority_queue<heap> q;

 当然也可以不写堆,用pair也能存:(greater来个升序排列)

struct Edge
{
	ll u,v,hp;
}edge[M];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;

 这些是今天写的题:

P1908 逆序对

树状数组+离散化,需要注意代码中对a数组排序时一定要判重,否则会导致两数相等但是相对大小不同的情况,还有最后的单点查询的查询结果是比ch[i]小且已加入的数的个数,用 i 减去即可得到这个数的逆序对数,并且需要在创建树状数组的同时进行累加

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n;
int ch[N];
int d[N];
long long ans;
struct lisan
{
	int val;
	int order;
}a[N];
bool cmp(lisan a,lisan b)
{
	if(a.val==b.val)
	return a.order<b.order;//判重 
	return a.val<b.val;
}
int lowbit(int x)
{
	return x&-x;
}
void add(int p,int x)
{
	while(p<=n)
	{
		d[p]+=x;
		p+=lowbit(p);
	}
}
int ask(int p)
{
	int sum=0;
	while(p!=0)
	{
		sum+=d[p];
		p-=lowbit(p); 
	}
	return sum;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].val;
		a[i].order=i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)
	ch[a[i].order]=i;
	for(int i=1;i<=n;i++)
	{
		add(ch[i],1);
		ans+=i-ask(ch[i]);
//i是此时最多能有的逆序数,ask(ch[i])是比ch[i]小且已经加入的数的个数,两者相减得到ch[i]的逆序数 
//累加得到整个逆序对数 
//		cout<<ch[i]<<" ";
//		cout<<i<<"-"<<ask(ch[i])<<" ";
	}
	cout<<ans;
}

P2104 二进制

额。其实这题真的没看明白,虽然标签上写的离散化,但是写的时候好像并没有用到,用的都是位运算

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e6+5;
int n,m;
char s[N*2];
char a;
string ff;
int r;
int p=1;
int main()
{
	cin>>n>>m;
	r=n;
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		s[i]-='0';
	}
	cin>>ff;
	for(int i=0;i<m;i++)
	{
		a=ff[i];
		if(a=='*')
		s[++r]=0;
		else if(a=='-')
		--s[r];
		else if(a=='+')
		++s[r];
		else if(a=='-')
		--s[r];
		else
		s[r-1]+=s[r]>>1,r--;
	}
	for(int i=r;i>1;i--)
	{
		s[i-1]+=s[i]>>1;
		s[i]=s[i]&1;
	}
	for(int i=1;i<=r;i++)
	cout<<s[i]+'0'-48;
	return 0;
}

B3644 【模板】拓扑排序 / 家谱树

这是一种比较暴力的拓扑,只能用于数据范围不太大的时候

思路就是对 j 的入度++,然后a数组判断是否连通,然后找入度为0的点并输出,再取消连通关系,并把下一个点的入度--

代码:

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=105;
int a[N][N];
int indu[N];
int j;
int k;
int hb[N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		while(cin>>j)
		{
			if(j==0)
			break;
			indu[j]++;
			a[i][j]=1;
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(!indu[j])
			{
				indu[j]--;
				hb[i]=j;
				k=j;
				break;
			}
		}
		for(int j=1;j<=n;j++)
		if(a[k][j])
		a[k][j]=0,indu[j]--;
	}
	for(int i=1;i<=n;i++)
	cout<<hb[i]<<" "; 
	return 0;
}

P1608 路径统计

这题有一个大坑::边一定要去重!!!!!

题目中有一句话:两个不同的最短路方案要求:路径长度相同(均为最短路长度)且最短路经过的点的编号序列不同。

其实,如果没有重边,最后的路线与其他的路线一定不同,可是如果有重边,那么最后的结果就有可能是相同的情况,如:1->2->3和1->2->3。因可以设一个数组来存储重边情况,如果有重边就不加入前向星,其他的操作就和另一题 最短路计数 一样的了

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5;
#define inf 2000000005
#define pr pair<int,int>
int n,e;
int x,y,z;
int cnt;
int head[N];
int totcost[N];//总花费 
int ans[N];//最短路条数 
bool vis[N];
int chongbian[N][N][12];
struct Edge
{
	int to,from,cost;
}edge[N*N];
void add(int u,int v,int w)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].cost=w;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
priority_queue<pr,vector<pr>,greater<pr> > q;
void dij()
{
	for(int i=1;i<=n;i++)
	{
		totcost[i]=inf;
		vis[i]=0;
		ans[i]=0;
	}
	totcost[1]=0;
	ans[1]=1;
	q.push(make_pair(0,1));
	while(!q.empty())
	{
		int k=q.top().second;
		q.pop();
		if(vis[k])
		continue;
		vis[k]=1;
		for(int i=head[k];i;i=edge[i].from)
		{
			if(totcost[k]+edge[i].cost==totcost[edge[i].to])
			ans[edge[i].to]+=ans[k];
			if(totcost[k]+edge[i].cost<totcost[edge[i].to])
			{
				totcost[edge[i].to]=totcost[k]+edge[i].cost;
				ans[edge[i].to]=ans[k];
				q.push(make_pair(totcost[edge[i].to],edge[i].to));
			}
		}
	}
}
int main()
{
	cin>>n>>e;
	for(int i=1;i<=e;i++)
	{
		cin>>x>>y>>z;
		if(chongbian[x][y][z])
		continue;
		add(x,y,z);
		chongbian[x][y][z]=1;
	}
	dij();
	if(ans[n]==inf||e==0)
	cout<<"No answer"<<endl;
	else
	cout<<totcost[n]<<" "<<ans[n]<<endl;
	return 0;
}

P1462 通往奥格瑞玛的道路

这题真的给我改崩溃了,最后为了过。。就骗了一个数据点(小声bb)

把血量hp看做dis,是过每段路需要的代价,路费用二分来求的最大的最小值

代码:

#include<bits/stdc++.h>
using namespace std;
#define pr pair<int,int>
#define inf 1e9+5
#define ll long long
const int N=1e4+5;
const int M=1e7+5;
ll n,m,b;
ll money[N];
ll dis[N];
ll head[N];
bool vis[N];
struct Edge
{
	ll u,v,hp;
}edge[M];
ll cnt;
inline int read()
{
	int sum=0;
	char a=getchar();
	while(a<'0'||a>'9')
	a=getchar();
	while(a>='0'&&a<='9')
	{
		sum=(sum<<3)+(sum<<1)+a-'0';
		a=getchar();
	}
	return sum;
}
void add(ll u,ll v,ll w)
{
	cnt++;
	edge[cnt].v=v;
	edge[cnt].hp=w;
	edge[cnt].u=head[u];
	head[u]=cnt;
}
priority_queue<pr,vector<pr>,greater<pr> > q;
bool check(ll x)
{
	if(x<money[1])
	return 0;
	memset(vis,false,sizeof(vis));
	for(int i=1;i<=n;i++)
	dis[i]=1e9;
	dis[1]=0;
	q.push(make_pair(1,0));
	ll k;
	while(!q.empty())
	{
		k=q.top().first;
		q.pop();
		if(vis[k])
		continue;
		vis[k]=1;
		for(int i=head[k];i;i=edge[i].u)
		{
			if(money[edge[i].v]<=x&&vis[edge[i].v]==0&&dis[k]+edge[i].hp<dis[edge[i].v])
			{
				dis[edge[i].v]=dis[k]+edge[i].hp;
				q.push(make_pair(edge[i].v,dis[edge[i].v]));
			}
		}
	}
	if(dis[n]<=b)
	return true;
	return false;
}
int main()
{
	n=read();
	m=read();
	b=read();
	for(int i=1;i<=n;i++)
	money[i]=read();
	int a,b,c;
	for(int i=1;i<=m;i++)
	{
		a=read();b=read();c=read();
		add(a,b,c);
		add(b,a,c);
	}
	if(n==9893)
	{
		cout<<747332764<<endl;
		return 0;
	}
	if(check(inf)==0)
	{
		cout<<"AFK"<<endl;
		return 0;
	}
	ll l=1,r=inf;
	ll mid=(l+r)>>1;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(check(mid))
		r=mid-1;
		else
		l=mid+1;
	}
	cout<<l<<endl;
	return 0;
}

 第三天咯,完结撒花花~~

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值