【暑假集训模拟DAY7】图论

前言

之前没写,现在补上

自认为图论还算是比较熟悉的一个专题,不过看到题之后还是思路都挺暴力的

虽然开局就统揽全局,但是写完了前3题的暴力之后才发现T4是签到题

期望得分:30+30+60+100=220pts

实际得分:0+0+60+90=150pts

好家伙暴搜全写炸了(没有自己造数据测试的下场)

题解

T1 recover

要不是知道这是图论,我绝对要在Trie树的道路上一去不返...

看了半天没什么思路,于是先打个暴搜再说!(可惜最终挂了)

正解:把一个字符串前两个向后两个连边,之后跑一遍欧拉路径就可以,算是一种构造的思想

关于欧拉路径...简单说一下怎么求:

在有向图里,记录(入度!=出度)的点的个数,如果为0则有欧拉回路,如果为2则有欧拉路径,如果是其他的值就不存在欧拉路径。此外,如果有abs(ind-oud)>1,也不存在欧拉路径

如何找到欧拉路径?写一个dfs,对于每个点,走一条边就把这条边删掉,在所有出边都删掉之后将该点压入栈(仔细想想,这样可以避免回溯的时候默认从儿子走回父亲),最终答案序列为弹栈序列

代码:

【模板】欧拉路径
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 1e5+10,M = 2e5+10;
int n,m,cnt,ind[N],oud[N],st;
vector<int> e[N];
stack<int> s;
void add(int x,int y)
{
	e[x].push_back(y);
	ind[y]++,oud[x]++;
}
void dfs(int u)
{
	while(!e[u].empty())
	{
		int v=e[u][0];
		e[u].erase(e[u].begin());
		dfs(v);
	}
	s.push(u);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	st=1;
	for(int i=1;i<=n;i++)
	{
		sort(e[i].begin(),e[i].end());
		if(abs(ind[i]-oud[i])>1) {printf("No\n");return 0;}
		if(oud[i]-ind[i]==1) st=i; 
		cnt+=ind[i]!=oud[i];
		
	}
	if(cnt!=0&&cnt!=2) {printf("No\n");return 0;}
	dfs(st);
	while(!s.empty())
		printf("%d ",s.top()),s.pop();
	return 0;
}

至于本题代码,由于交不了就不贴了

T2 team

是个人都会想到并查集,然而进一步我就不会了(不过看有人写并查集好像也能过,我这里没考虑),考场写的暴力(还挂了)

正解:首先把原图转化成补图(这步看似不起眼但是逆向思维很关键,有了这步才能进一步转化),问题转化为相连的两个点不能是同一组,也就是二分图

所以可以知道,有解的情况一定是一个二分图。现在假设要选出一组人,对于每一个连通块,两种颜色的点可以任选其一,于是就是一个分组数量为2的分组背包

还有别忘了特判n==1的情况输出No solution

代码实现简洁明了(比std好看多了)

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 105;
int head[N*N<<1],ecnt,siz[N][2],tot,col[N];
int dp[N][N],T,n,mp[N][N];
bool flag,vis[N];
struct edge
{
	int nxt,to;
}a[N*N<<1];
void add(int x,int y)
{
	a[++ecnt].nxt=head[x];
	a[ecnt].to=y;
	head[x]=ecnt;
}
void init()
{
	memset(siz,0,sizeof(siz));
	memset(head,-1,sizeof(head));
	memset(vis,0,sizeof(vis));
	memset(col,0,sizeof(col));
	memset(dp,0,sizeof(dp));
	ecnt=-1,tot=0,flag=0;
}
void dfs(int u,int fa)
{
	siz[tot][col[u]]++;
	vis[u]=1;
	if(flag) return;
	for(int i=head[u];~i;i=a[i].nxt)
	{
		int v=a[i].to;
		if(v==fa) continue;
		if(vis[v]&&col[v]==col[u]) {flag=1;return;} 
		if(vis[v]) continue;
		col[v]=col[u]^1;
		dfs(v,u);
	}

}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		init();
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
			{
				scanf("%d",&mp[i][j]);
				if(!mp[i][j]) add(i,j),add(j,i);
			}
		if(n==1) {printf("No solution\n");continue;}
		for(int i=1;i<=n;i++)
			if(!vis[i]) tot++,dfs(i,-1);
		if(flag) {printf("No solution\n");continue;}
		dp[0][0]=1;
		for(int i=1;i<=tot;i++)
			for(int j=n;j>=0;j--)
			{
				if(j>=siz[i][0]&&dp[i-1][j-siz[i][0]]) dp[i][j]=1;
				if(j>=siz[i][1]&&dp[i-1][j-siz[i][1]]) dp[i][j]=1;
			}
		int res=INF;
		for(int i=1;i<=n;i++)
			if(dp[tot][i]) res=min(res,(int)abs(n-2*i));
		printf("%d\n",res);
	}
	return 0;
}
/*
1
6
1 1 0 1 1 0 
1 1 1 0 1 0 
1 0 1 1 0 1 
0 0 1 1 0 1 
1 1 0 1 1 1 
1 1 1 1 1 1 
*/

T3 plan

有了之前的教训,第一时间想离线,然而并不知道该怎么离

经过一番苦思冥想......最终写了暴力---边权改为时间的SPFA

正解:每次多一条边之后,其他所有点到这条边端点的最早到达时间都可能变化,如果反着存,那就变成了这两个端点到剩下所有联通的点的最早到达时间都可能变化,更好操作,所以反着存,把询问按出发时间降序排序,如果这段此时联通且最早到达时间少于询问的到达时间,就可以更新上询问的答案了

实际上和图论关系不大,关键在于离线,还有这种时间逆流的逆向思路值得学习(其实已经出现过好多次了啊喂)

代码:

考场暴力代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 1005,M = 2e5+10;
#define pr pair<int,int>
#define mp make_pair
bool vis[N],visn[N];
queue<int> q;
struct ask
{
	int l,r,s,t;
}ques[M];
int n,m,Q,ecnt=-1,head[M],ti[N];
struct edge 
{
	int nxt,to,w;
}a[M];
void add(int x,int y,int w)
{
	a[++ecnt].nxt=head[x];
	a[ecnt].to=y;
	a[ecnt].w=w;
	head[x]=ecnt;
}
void solve(int l,int r,int s,int t)//SPFA
{
	memset(vis,0,sizeof(vis));
	memset(visn,0,sizeof(visn));
	memset(ti,0x3f,sizeof(ti));
	q.push(s);
	vis[s]=1,visn[s]=1,ti[s]=l;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		vis[u]=0;
		for(int i=head[u];~i;i=a[i].nxt)
		{
			int v=a[i].to;
		
			//if(vis[v]) continue;
			if(a[i].w>=ti[u]&&a[i].w<=r) 
			{
			    visn[v]=1;
				if(a[i].w<ti[v]) 
				{
					ti[v]=a[i].w;
					if(!vis[v]) q.push(v),vis[v]=1;
				}
				//if(v==t&&ti[v]<=r) 
			
			}
		}
	}
}
int main()
{
	//freopen("plan.in","r",stdin);
	//freopen("plan.out","w",stdout); 
	memset(head,-1,sizeof(head));
	scanf("%d%d%d",&n,&m,&Q);
	for(int i=1;i<=m;i++)	
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v,i);
		add(v,u,i); 
	}
	for(int i=1;i<=Q;i++)
	{
		scanf("%d%d%d%d",&ques[i].l,&ques[i].r,&ques[i].s,&ques[i].t);
		solve(ques[i].l,ques[i].r,ques[i].s,ques[i].t);
		if(visn[ques[i].t]) printf("Yes\n");
		else printf("No\n"); 
	}
	
	return 0;
}
/*
5 4 6
1 2
2 3
3 4
3 5
1 3 1 4 
1 3 2 4 
1 4 4 5
1 4 4 1 
2 3 1 4
2 2 2 3
*/
AC代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 1005,M = 2e5+10;
#define pr pair<int,int>
#define mp make_pair
bool vis[N],visn[N],jd[M];
int ans[N][N],u[M],v[M];
queue<int> q;
struct ask
{
	int l,r,s,t,id;
}ques[M];
int n,m,Q,ecnt=-1;
bool cmp(ask a,ask b)
{
	return a.l>b.l;
}
int main()
{
	//freopen("plan.in","r",stdin);
	//freopen("plan.out","w",stdout); 
	scanf("%d%d%d",&n,&m,&Q);
	for(int i=1;i<=m;i++)	
	{
		scanf("%d%d",&u[i],&v[i]);
	}
	for(int i=1;i<=Q;i++)
	{
		scanf("%d%d%d%d",&ques[i].l,&ques[i].r,&ques[i].s,&ques[i].t);
		ques[i].id=i;
	}
	sort(ques+1,ques+Q+1,cmp);
	memset(ans,0x3f,sizeof(ans));
	int lst=1;
	for(int i=m;i>=1;i--)
	{
		ans[u[i]][v[i]]=ans[v[i]][u[i]]=i;
		for(int j=1;j<=n;j++)
			ans[u[i]][j]=min(ans[v[i]][j],ans[u[i]][j]),
			ans[v[i]][j]=min(ans[u[i]][j],ans[v[i]][j]);
		for(int j=lst;j<=Q;j++)	
		{
			if(ques[j].l==i) jd[ques[j].id]=(ans[ques[j].s][ques[j].t]<=ques[j].r);
			else {lst=j;break;}
		}
	}
	for(int i=1;i<=Q;i++)
		if(jd[i]) printf("Yes\n");
		else printf("No\n");
	return 0;
	/*
	int lst=m;
	for(int i=1;i<=Q;i++)
	{
		int id=ques[i].id;
		if(lst!=ques[i].l)
		for(int j=ques[i].l;j<=lst;j++) 
		{ 
			ans[u[j]][v[j]]=ans[v[j]][u[j]]=j;
			for(int k=1;k<=n;k++) 
				ans[u[j]][k]=ans[k][u[j]]=min(ans[v[j]][k],ans[u[j]][k]),
				ans[v[j]][k]=ans[k][v[j]]=min(ans[u[j]][k],ans[v[j]][k]);
		}
		lst=ques[i].l;
		if(ans[ques[i].s][ques[i].t]<=ques[i].r) jd[id]=1;
		else jd[id]=0;
	}
	*/

}
/*
5 4 6
1 2
2 3
3 4
3 5
1 3 1 4 
1 3 2 4 
1 4 4 5
1 4 4 1 
2 3 1 4
2 2 2 3
*/

T4 seqpath

初看有点被唬到,再一看发现就是一遍BFS完事

不过还是挂到了90pts----因为没有考虑到要按照规定路径上点的顺序连边,而是用set判断这个点能不能走

就只有这么一点甚至称不上细节的细节

其他的就很轻松了

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 5e4+10,M = 4e5+10;
int n,T,m,k,tar[N],head[M],ecnt;
int dis[N],vis[N];
multiset<int> s;
queue<int> q;
void init()
{
	memset(head,-1,sizeof(head));
	ecnt=-1;
	s.clear();
}
struct edge 
{
	int nxt,to;
}a[M];
void add(int x,int y)
{
	a[++ecnt].nxt=head[x];
	a[ecnt].to=y;
	head[x]=ecnt;
}
void bfs()
{
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis)); 
	q.push(1);
	vis[1]=1,dis[1]=0;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=head[u];~i;i=a[i].nxt)
		{
			int v=a[i].to;
			if(vis[v]||s.find(v)==s.end()) continue;
			vis[v]=1;
			dis[v]=dis[u]+1;
			q.push(v);
		}
	}
}
int main()
{
	//freopen("seqpath.in","r",stdin);
	//freopen("seqpath.out","w",stdout);
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d",&n,&m,&k);
		init();
		for(int i=1;i<=k+1;i++)
		{
			scanf("%d",&tar[i]);
			s.insert(tar[i]);
			if(i!=1) add(tar[i],tar[i-1]),add(tar[i-1],tar[i]); 
		}
		for(int i=1;i<=m-k;i++)	
		{
			int u,v;
			scanf("%d%d",&u,&v);
			//if(v==u) continue;
			add(u,v),add(v,u);
		}
		bfs();
		printf("%d\n",dis[n]);
		
	}
	return 0;
}
/*
2
7 8 5
1 2 3 4 5 7
1 3
3 6 
6 7
7 8 5
1 2 3 4 5 7
1 3
3 5 
6 7
*/
posted @ 2021-08-21 23:34  conprour  阅读(25)  评论(0编辑  收藏  举报