ACM图论+数据结构杂题总结

ACM:图论+数据结构杂题总结

T1:

题目描述:(出处:Atcoder Regular Contest 067 Yakiniku Restaurants)

一条街上有N家烧烤餐馆。餐厅从西到东编号为1到N,I餐厅和i+1餐厅之间的距离为Ai。

乔伊辛诺有M张票,编号从1到M。每个烧烤餐厅都提供烧烤餐来交换这些票。餐厅i提供美味的Bi,j餐,以换取j票。每张票只能使用一次,但是餐厅可以使用任意数量的票。

乔伊辛诺想从她选择的一家餐厅开始,然后反复去另一家烧烤餐厅,并在她目前所在的餐厅使用未用过的票,来享用M份烧烤餐。

她的最终幸福是通过以下公式来计算的:(所吃食物的总美味)-(旅行的总距离)。

找到她最大可能的最终幸福。

数据范围

  • 所有输入值都是整数。
  • 2≤N≤5×103
  • 1≤M≤200
  • 1≤Ai≤109
  • 1≤Bi,j≤109

输入

标准输入以以下格式给出输入:

N M

A1 A2 … AN−1

B1,1 B1,2 … B1,M

B2,1 B2,2 … B2,M

:

BN,1 BN,2 … BN,M

输出:

乔伊辛诺最终可能获得的最大幸福。

input1

3 4
1 4
2 2 5 1
1 3 3 2
2 2 5 1

output1

11

【解析】:考虑暴力枚举区间(必然从一个端点走向另一个端点),对于每一个票 j j j选择价值最大的那个店购买,时间复杂度为(O(n2 m))

这样就得出一个显然的贪心结论:在一个区间里,对于票 j j j,一定会选择最大 B [ i , j ] B[i,j] B[i,j] i i i在区间中)。那么不妨来考虑每一个 B [ i , j ] B[i,j] B[i,j]能够带来的贡献。

f [ i ] [ j ] f[i][j] f[i][j]表示在区间 [ i , j ] [i,j] [i,j]花完 m m m张票的最大价值,很显然路程代价为区间长度。

那么处理出一定在i店买j票的最大区间为 [ a , b ] [a,b] [a,b],即 a < = i < = b a<=i<=b a<=i<=b a − 1 a-1 a1 B [ a − 1 , j ] > B [ i , j ] B[a-1,j]>B[i,j] B[a1,j]>B[i,j]的最大位置, b + 1 b+1 b+1 B [ b + 1 , j ] > B [ i , j ] B[b+1,j]>B[i,j] B[b+1,j]>B[i,j]的最小位置,这个东西可以用单调栈维护出来 a , b a,b ab。那么一定在i点买j票的区间就有左端点属于 [ a , i ] [a,i] [a,i],右端点属于 [ i , b ] [i,b] [i,b],然后在对应的每一个f数组上累计贡献,发现这其实一个区域矩形累加,利用二维差分来实现。【把 f f f数组看成一个矩阵,横坐标为左端点,纵坐标为右端点,那么对于的所有区间就是以 [ a , i ] [a,i] [a,i]为左下角,以 [ i , b ] [i,b] [i,b]为右上角的大矩形(针对第一象限)】

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=5e3+10;
int stk[N],n,m,l[N][210],r[N][210],a[N][205],top=0;
//l[i][j]--左边第一个>B[i,j]的位置  r[i][j]--右边以第一个>B[i,j]的位置
ll dis[N],f[N][N],ans=0;
inline void add(int r1,int c1,int r2,int c2,ll v)//二维差分trick---划重点
{
	f[r1][c1]+=v; f[r1][c2+1]-=v; f[r2+1][c1]-=v; f[r2+1][c2+1]+=v;
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
  scanf("%d%d",&n,&m);
  for(int i=2;i<=n;i++) scanf("%d",&dis[i]),dis[i]+=dis[i-1];//处理距离
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
  top=0; 
  for(int i=1;i<=m;i++)//第i张票 
  {
	 top=0;
	 for(int j=1;j<=n;j++)
	 {
	   while(top&&a[stk[top]][i]<a[j][i]) top--;
	   l[j][i]=(top?stk[top]:0);
	   stk[++top]=j; 
     }
     top=0;
     for(int j=n;j>=1;j--)
     {
     	while(top&&a[stk[top]][i]<a[j][i]) top--;
     	r[j][i]=(top?stk[top]:n+1);
     	stk[++top]=j;
	 }
     for(int j=1;j<=n;j++)
       add(l[j][i]+1,j,j,r[j][i]-1,a[j][i]);
  } 
  for(int i=1;i<=n;i++)//前缀和:将差分还原成原矩阵
    for(int j=1;j<=n;j++)
      {
      	f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
      	if(j>=i) ans=max(ans,f[i][j]-(dis[j]-dis[i]));
	  }
  printf("%lld\n",ans);
  return 0;
}

T2:

题目描述:(出处:Atcoder Regular Contest 080 Young Maids)

给定一个n(n为偶数)的p排列,有一个空队列q,每次从p中删除两个相邻的数插入q的队头,进行n/2次以后p排列为空了,问q队列最终的排列的最小字典序是什么?

数据范围: 1 < = n < = 2 e 5 1<=n<=2e5 1<=n<=2e5

【解析】:倒着贪心,最后我们放在最前面的,就是最小的奇数位的数加上最小的偶数位的数。

(奇数位前面保证完全匹配,偶数位后面保证完全匹配,不会有剩余)

然后我们放完这段之后,我们发现我们把原来的区间就会砍为三段,然后再在每一段找到最小的两个数即可。不停的贪心下去就好了。

奇偶数位的最小值可以用ST来维护,用一个优先堆来存放两个二元组:{{在选择区间下的最小奇数位,在选择区间下的最小偶数位(相对选择区间)},{选择区间的左端点,选择区间的右端点}}(既然我们已经采用了奇偶性选择数,就能够保证当前选择区间以外的区间能都完全匹配,那么只需要考虑当前区间左端点的奇偶【如:一开始 [ 1 , n ] [1,n] [1,n]区间一开始找最小奇数位(此时选择区间的左端点为奇数),再找最小偶数位】)

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=2e5+7;
int n,p[N],f[2][N][20],Log[N],pos[N]; 
inline int ask(int opt,int l,int r)
{
	int k=Log[r-l+1];
	return min(f[opt][l][k],f[opt][r-(1<<k)+1][k]);
}
inline pair<int,int> cal(int l,int r)
{
	int x,y;
	x=ask(l&1,l,r);//相对
	y=ask((l-1)&1,pos[x]+1,r);
	return make_pair(-x,-y);
}
priority_queue<pair<pair<int,int>,pair<int,int > > > q;
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
  scanf("%d",&n);
  for(int i=1;i<=n;i++) 
  {
  	scanf("%d",&p[i]);
  	f[i&1][i][0]=p[i];
  	f[(i-1)&1][i][0]=1e9+7;
  	pos[p[i]]=i;
  }
  Log[1]=0;
  for(int i=2;i<=N-7;i++)
    Log[i]=Log[i>>1]+1;
  for(int k=0;k<=1;k++)//奇or偶
    for(int j=1;j<=19;j++)
	  for(int i=1;i+(1<<j)-1<=n;i++)
	    f[k][i][j]=min(f[k][i][j-1],f[k][i+(1<<(j-1))][j-1]);
  q.push({{cal(1,n)},{1,n}}); 
  while(q.size())
  {
  	pair<pair<int,int>,pair<int,int> > tmp=q.top(); q.pop(); //cout<<tmp.second.first<<endl;
  	int x=tmp.first.first,y=tmp.first.second; 
  	printf("%d %d ",-x,-y);
  	x=pos[-x],y=pos[-y];
  	int l=tmp.second.first,r=tmp.second.second; 
  	//cout<<l<<" "<<r<<" "<<x<<" "<<y<<endl;
  	if(l+1<x) q.push({{cal(l,x-1)},{l,x-1}});
  	if(x+1<y-1) q.push({{cal(x+1,y-1)},{x+1,y-1}});
  	if(y+1<r) q.push({{cal(y+1,r)},{y+1,r}});
  }
  return 0;
}

T3:再见!

(出处:codeforces gym 101955 E The Kouga Ninja Scrolls)

T4:

题面描述(出处:Atcoder Grand Contest 012 Splatter Painting)

Squid 喜欢在图中为一些顶点染色(毕竟是鱿鱼…)

现在有一张由N个顶点和M条边组成的简单无向图,其

中顶点编号为1到N。我们用数字来编号各种颜色。

一开始,所有的顶点都会被染成颜色0。第i条双向边

连接着两个端点 a [ i ] a[i] a[i] b [ i ] b[i] b[i]。每条边的长度都是单位1。

Squid会在这张图上进行Q次操作。其中对于第i次操作,

他会将与顶点 v [ i ] v[i] v[i]相距 d [ i ] d[ i ] d[i] 以内(包括 v [ i ] v[i] v[i])的所

有点重新染色成颜色 c [ i ] c[i] c[i]

请问Q次操作后,每个顶点的颜色各是什么。

数据范围

1≤N,M,Q≤105

1≤ai,bi,vi≤N

ai≠bi

0≤di≤10

1≤ci≤105

di 和 ci 是整数

没有自环和重边

Input

输入来自标准输入,格式如下:

N M
a1 b1
:
aM bM
Q
v1 d1 c1
:
vQ dQ cQ

Output

答案一共 N 行。在第 i 行中,在 Q 操作之后输出顶点 i 的颜色。

【解析】:由于节点会被最后染的颜色所覆盖,所以我们考虑倒着染色,已经染过色的节点就不用再考虑了。然后记忆化搜索,设 f [ i ] [ j ] f[i][j] f[i][j]表示距离i节点距离为j的颜色。

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1e5+7;
int tot,n,m,q,v[N],d[N],c[N],first[N];
int f[N][11];//距离i点为j的点的颜色 
struct fuk
{
	int x,next;
}a[N<<2];
inline void add(int x,int to)
{
	tot++;
	a[tot].next=first[x]; a[tot].x=to; first[x]=tot;
}
void dfs(int x,int d,int col)
{
	if(d==-1) return;
	if(f[x][d]) return;
	f[x][d]=col; 
	for(int i=first[x];i;i=a[i].next)
	{
		int y=a[i].x;
		dfs(y,d-1,col);
	}
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++)
  {
  	int x,y; scanf("%d%d",&x,&y);
  	add(x,y); add(y,x);
  } 
  for(int i=1;i<=n;i++) add(i,i);//这一步操作至关重要,保证可以在d距离之内全部染色 
  scanf("%d",&q);
  for(int i=1;i<=q;i++) scanf("%d%d%d",&v[i],&d[i],&c[i]);
  for(int i=q;i>=1;i--)
	dfs(v[i],d[i],c[i]); 
  for(int i=1;i<=n;i++)
    printf("%d\n",f[i][0]);
  return 0;
}

T5:

题目描述:(出处:codeforces 567 E President and Roads)

给定一个n个点,m条边的有向图,询问每一条的状态

1.一定在起点s到终点t的最短路上:输出“YES”

2.通过减少x(x<边权)使得一定在最短路上:输出"CAN x"

3.前面2则都不满足:输出"NO"

2<=n<=1e5,1<=m<=1e5,1<=s,t<=n,1<=边权<=1e6;

【解析】:

一个点x在最短路上的条件: d i s [ s ] [ x ] dis[s][x] dis[s][x]+ d i s [ x ] [ t ] dis[x][t] dis[x][t]= d i s [ s ] [ t ] dis[s][t] dis[s][t]

一个点x一定在最短路上的条件: a n s [ s ] [ x ] ans[s][x] ans[s][x]* a n s [ x ] [ t ] ans[x][t] ans[x][t]= a n s [ s ] [ t ] ans[s][t] ans[s][t](其中 a n s [ i ] [ j ] ans[i][j] ans[i][j]表示i到j的最短路的方案数)

d i s [ s ] [ x ] dis[s][x] dis[s][x] a n s [ s ] [ x ] ans[s][x] ans[s][x]我们可以一遍堆优化dj的时候顺便求出。

明显 d i s [ x ] [ t ] dis[x][t] dis[x][t] a n s [ x ] [ t ] ans[x][t] ans[x][t]我们都可以通过建反图同理求出。

然后就是分类讨论即可。

坑点:方案数很大可能会爆ll,所以要模一个大质数。(但不知道为什么不能是1e9+7)…然后dis数组权值一开始赋值大一点…

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1e5+7,mod=998244353;
const ll INF=1e15;
inline ll read()
{
  ll sum=0; char ch;
  while(ch<'0'||ch>'9') ch=getchar();
  while(ch>='0'&&ch<='9') sum=(sum<<1)+(sum<<3)+ch-'0',ch=getchar();
  return sum;	
} 
inline ll Add(ll a,ll b){return (ll)a+b>=mod?a+b-mod:a+b;}
ll n,m,s,e,tot1,tot2,first1[N],first2[N],vis[N];
ll dis[2][N],ans[N],sna[N];//0--ZHENG 1--FAN
struct fuk
{
	ll x,next;
	ll v;
}a[N<<1],b[N<<1];
struct fuc
{
	ll u,v;
	ll w;
}c[N];
inline void add1(ll x,ll to,ll v)
{
	tot1++;
	a[tot1].x=to; a[tot1].next=first1[x]; first1[x]=tot1; a[tot1].v=v;
}
inline void add2(ll x,ll to,ll v)
{
	tot2++;
	b[tot2].x=to; b[tot2].next=first2[x]; first2[x]=tot2; b[tot2].v=v;
}
inline void DJ1()
{
	priority_queue<pair<ll,int> >q;
	while(q.size()) q.pop();
	for(int i=1;i<=n;i++) dis[0][i]=INF,vis[i]=0;
	q.push(make_pair(0,s)); dis[0][s]=0,ans[s]=1;
	while(q.size())
	{
		int x=q.top().second; q.pop();
		if(vis[x]) continue; vis[x]=1;
		for(int i=first1[x];i;i=a[i].next)
		{
			int y=a[i].x;
			if(dis[0][y]>dis[0][x]+a[i].v)
			{
				dis[0][y]=dis[0][x]+a[i].v;
				q.push(make_pair(-dis[0][y],y));
				ans[y]=ans[x];
			}
			else if(dis[0][y]==dis[0][x]+a[i].v)
			  ans[y]=Add(ans[y],ans[x]);
		}
	}
}
inline void DJ2()
{
	priority_queue<pair<ll,int> >q;
	while(q.size()) q.pop();
	for(int i=1;i<=n;i++) dis[1][i]=INF,vis[i]=0;
	q.push(make_pair(0,e)); dis[1][e]=0,sna[e]=1;
	while(q.size())
	{
		int x=q.top().second; q.pop();
		if(vis[x]) continue; vis[x]=1;
		for(int i=first2[x];i;i=b[i].next)
		{
			int y=b[i].x;
			if(dis[1][y]>dis[1][x]+b[i].v)
			{
				dis[1][y]=dis[1][x]+b[i].v;
				q.push(make_pair(-dis[1][y],y));
				sna[y]=sna[x];
			}
			else if(dis[1][y]==dis[1][x]+b[i].v)
			  sna[y]=Add(sna[y],sna[x]);
		}
	}
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
  n=read(),m=read(); s=read(); e=read();
  for(int i=1;i<=m;i++)
  {
  	ll x=read(),y=read(),z=read();
  	add1(x,y,z); add2(y,x,z);
  	c[i].u=x,c[i].v=y,c[i].w=z;
  }
  DJ1(); DJ2();
  for(int i=1;i<=m;i++)
    {
    	ll x=c[i].u,y=c[i].v;
    	if(dis[0][x]+dis[1][y]+c[i].w==dis[0][e])
    	{
    		if((ll)ans[x]*sna[y]%mod==ans[e])
    		{
    			puts("YES");
			}
			else if(c[i].w>1) puts("CAN 1");
			else puts("NO");
		}
		else
		if(dis[0][x]+dis[1][y]-dis[0][e]+1>=0)
		{
			puts("NO");
		}
		else printf("CAN %lld\n",dis[0][x]+dis[1][y]+c[i].w-dis[0][e]+1);
	}
  return 0;
}

//吐槽:老刘总是说这样重复的可以写成一个函数,但是我就是懒233

T6:

题目描述:(出处:codeforces 1205 B Shortest Cycle)

已知n个整数a1 a2…an。考虑n个节点上的图,其中i, j (i≠j)相互连接仅当ai and aj≠0时,其中and表示按位与运算。

求出这个图中最短环的长度(>=3),如果没有环,输出-1。

1 < = n < = 1 e 5 , 0 < = a i < = 1 e 18 1<=n<=1e5,0<=ai<=1e18 1<=n<=1e5,0<=ai<=1e18(接近2^64)

【解析】:根据抽屉原理,0不算在内,数量如果>2*64,那么一定存在一个二进制位有三个数都是1,即存在最小环=3.

否则,你会发现数量很少,直接各种求最小环。

(这里使用的是floyd判环(划重点))

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+7;
const int INF=1e5+7;
int n,tot=0;
ll res[N];
int a[201][201],f[201][201];
int ans=N;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		ll x; scanf("%lld",&x);
		if(x) res[++tot]=x;
	}
	if(tot>2*64)
	{
		puts("3");
		return 0;
	}
	n=tot;
	for(int i=1;i<=n;i++)
	  for(int j=i+1;j<=n;j++)
	    if((res[i]&res[j])!=0)
	    {
	    	a[i][j]=a[j][i]=f[i][j]=f[j][i]=1;
		}
		else a[i][j]=a[j][i]=f[i][j]=f[j][i]=INF;
	for(int k=1;k<=n;k++)
	{
	  for(int i=1;i<k;i++)
	    for(int j=i+1;j<k;j++)
	      ans=min(ans,f[i][j]+a[i][k]+a[k][j]);
	  for(int i=1;i<=n;i++)
	    for(int j=1;j<=n;j++)
	      f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
	}
	printf("%d\n",ans==INF?-1:ans);
} 
T7:

题面描述:(出处:codeforces gym 101257 F ISlands II)

给出一个n*m的地图,上面相同数字的代表一个国家,问对于每个国家有多少个国家在它内部(即被包围)。例如样例,1包围2,2包围3,所以1包围2和3,2包围3。

(1<=n,m<=1000)

样例:

1 1 1 1 1 1

1 2 2 2 2 1

1 2 4 3 2 1

1 2 3 3 2 1

1 2 2 2 2 1

1 1 1 1 1 1

【解析】:对于一个国家,将和它相邻的国家连边,最后形成一个图。可以发现图中的割点一定包围了一些国家。如图中的2号点,但是无法确定包围了1还是包围了3,4,所以我们再在最外面加一圈0,从0开始dfs,发现一个割点,那么割点一定包围了不在0一端的国家。

0 0 0 0 0 0 0 0

0 1 1 1 1 1 1 0

0 1 2 2 2 2 1 0

0 1 2 4 3 2 1 0

0 1 2 3 3 2 1 0

0 1 2 2 2 2 1 0

0 1 1 1 1 1 1 0

0 0 0 0 0 0 0 0

图变成这样了。

于是就可以使用tarjan来找割点,割点就包围了一些国家。一开始dfs一遍,维护一个size代表子树的大小。然后如果该点是割点,就可以加上其子树的大小。

PS:不用考虑0号点是否是割点,数组开大,国家数不止1000。

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1000005;
int tot,vis[N],m[1005][1005],n,M,size[N],dfn[N],low[N],cnt=0,ans[N],first[N];
struct fuk
{
	int x,next;
}a[N<<3];//8倍了解一下
inline void add(int x,int to)
{
	tot++;
	a[tot].x=to; a[tot].next=first[x]; first[x]=tot;
}
void dfs(int x)
{
	size[x]=1; vis[x]=1;
	for(int i=first[x];i;i=a[i].next)
	{
		int y=a[i].x;
		if(vis[y]) continue;
		dfs(y);
		size[x]+=size[y];
	}
}
void tarjan(int x)
{
   dfn[x]=low[x]=++cnt;
   for(int i=first[x];i;i=a[i].next)
   {
   	int y=a[i].x;
   	if(!dfn[y])
   	{
   	    tarjan(y);
		low[x]=min(low[x],low[y]);
		if(dfn[x]<=low[y]) ans[x]+=size[y];
	}
	else low[x]=min(low[x],dfn[y]);
   }
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
  scanf("%d%d",&n,&M);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=M;j++)
      scanf("%d",&m[i][j]);
  for(int i=0;i<=n;i++)//包0 
    for(int j=0;j<=M;j++)
    {
    	if(m[i][j]!=m[i][j+1]) add(m[i][j],m[i][j+1]),add(m[i][j+1],m[i][j]);
    	if(m[i][j]!=m[i+1][j]) add(m[i][j],m[i+1][j]),add(m[i+1][j],m[i][j]);
	}
  dfs(0); tarjan(0);
  for(int i=1;i<=cnt-1;i++)
    printf("%d ",ans[i]);
  return 0;
}

总结反思:题做的还是不够多,模型的建立还是不够熟练,还是要多多思考。

CSP ++RP

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值