【清北学堂国庆刷题班】 Day4

【清北学堂国庆刷题班】 Day4

T1 油箱

【问题描述】

有 n个城市,每个城市都有加油站,有 m 条单向道路,距离为 x 的道路需要消耗 x 升的汽油。请问你的车辆可以携带的最小油箱容量,使得不限加油次数的情况下,无论你在哪个城市都可以到达任意的城市。

【输入格式】

第一行两个正整数n,m。

接下来 m 行,每行三个正整数x,y,z 表示一条 x 到 y 的有向边,边权为 z。

【输出格式】

输出符合条件的最小油箱容量,如果无法满足输出 −1

【样例输入】

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

【样例输出】

‮4

【样例解释】

油箱容量为4时,1→2,1→3,3→1,2→3 道路可以通行,此时满足无论你在哪个城市都可以到达任意的城市。

【数据规模与约定】

20% 的数据 \(n\le 5 ,m\le 20\)

40% 的数据 \(n\le 50,m\le 200\)

60% 的数据 \(n\le 5*10^2,m\le 2*10^3\)

100% 的数据 \(n\le 5*10^4,m\le 2*10^9\)保证 \(1\le z \le 10^9\)

思路

这道题目我们可以看出来要求油箱的最小容量,这个油箱的容量是刚好大于某个数,因此我们需要在整个定义域中找到这个刚好的容量,所以题目要求的是将最大值最小化,所以我们使用二分算法

问题变成判断一个有向图是否任意两点联通

相当于判断点1是否能到达所有点并且所有点都能到达点1

可以建立正向图和反向图,并从点1开始dfs遍历

也可以直接使用tarjan

(这两个我只会第一个)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50010
#define M 200010
using namespace std;
int n,m,x[M],y[M],z[M];
struct edge{int next,to;};
struct Graph
{
	int head[N],tot;
	bool vis[N];
	edge e[M];
	void clear()//初始化
	{
		tot=0;
		memset(head,0,sizeof(head));
		memset(vis,0,sizeof(vis));
	}
	void add(int u,int v)//加一条从u到v的边
	{
		e[++tot]=(edge){head[u],v};
		head[u]=tot;
	}
	void dfs(int x)
	{
		if(vis[x])return;
		vis[x]=1;
		for(int i=head[x];i;i=e[i].next)
			dfs(e[i].to);
	}
	bool check()//遍历整个图;
	{
		dfs(1);
		for(int i=1;i<=n;i++)
			if(!vis[i])
				return false;
		return true;
	}
}E1,E2;
bool check(int cost)//二分判断函数
{
	E1.clear();
	E2.clear();
	for(int i=1;i<=m;i++)
		if(z[i]<=cost)
		{
			E1.add(x[i],y[i]);
			E2.add(y[i],x[i]);
		}
	return E1.check()&&E2.check();
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&x[i],&y[i],&z[i]);
	int l=1,r=1e9,ans=2e9;
	while(l<=r)//开始二分
	{
		int mid=l+r>>1;
		if(check(mid))ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d\n",ans==2e9?-1:ans);
    return 0;
}

T2 求和

【问题描述】

给定一颗n个点组成的树,根节点为1号节点,每个点上有两个权值ai, bi。共有m次询问,每次询问给出两个正整数x, y,从x到y的路径设为t1,t2,…,tk 要求输出

\[\sum_{1\le i\le j \le k} a_{ti}*b_{tj} \]

【输入格式】

第一行两个正整数n,m

第二行n-1个数 f2,f3,…,fn,表示点i的父亲(fi< i)

接下来两行,每行n个数 分别表示ai,bi

接下来m行,每行两个正整数xi,yi ,表示第i次询问

【输出格式】

对于每个询问操作,输出答案。

【样例输入】

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

【样例输出】

2
11
35
85

【数据规模与约定】

20% \(n,m\le 100\)

40% \(n,m \le 2000\)

另20% 保证 \(a_i=b_i\)

另20% 保证\(f_i=i-1\),\(x\le y\)

100% \(n,m\le 10^5\)保证 \(1\le a_i,b_i\le 10^4\)

思路

20%:

\(O(n^2m)\)暴力,我暴力都不知道怎么解决怎么办?

40%:

和式可以优化成O(n)每次多一个点,新增的为\(\sum_{1\le i<j}a_{t_i}*b_{t_j}\)

\(a_i=b_i\)时:

\(\sum_{i\le i<j\le k}a_{t_i}*a_{t_j}=\frac {[(\sum_{1\le i\le k}a_{t_i})^2-\sum_{i\le i\le k}a_{t_i}^2]}{2}\)

\(f_i=i-1\)

序列查询,两个区间合并成一个新的区间

考虑怎么将两个区间合并

新区间中每个(i,j)都会被统计答案,每个(i,j)只有三种可能

同属左区间、同属右区间、i在左区间j在右区间

前两种的总和就是两个区间答案的和

第三种就是左区间a的和乘上右区间b的和。

100%:

将上述做法拓展到树上倍增就可以了

上面的我都不会,把题解先贴上去了,到时候再问吧。。。

代码

#include<cstdio>
#include<algorithm>
#define N 100010
#define ll long long
using namespace std;
struct node
{
	int a,b;
	ll s;
	node(){a=b=s=0;}
	node(int _a,int _b,ll _s):a(_a),b(_b),s(_s){}
};
node operator+(node x,node y)
{
	return node(x.a+y.a,x.b+y.b,x.s+y.s+(ll)x.a*y.b);
}
int n,m,head[N],tot,fa[N][17],deep[N];
node u[N][17],d[N][17];
int climb(int x,int dis)
{
	for(int i=16;~i;i--)
		if((dis>>i)&1)
			x=fa[x][i];
	return x;
}
int lca(int x,int y)
{
	if(deep[x]<deep[y])swap(x,y);
	x=climb(x,deep[x]-deep[y]);
	if(x==y)return x;
	for(int i=16;~i;i--)
	if(fa[x][i]!=fa[y][i])
	x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
node up(int x,int dis)
{
	node tmp;
	for(int i=16;~i;i--)
		if((dis>>i)&1)
		{
			tmp=tmp+u[x][i];
			x=fa[x][i];
		}
	return tmp;
}
node down(int x,int dis)
{
	node tmp;
	for(int i=16;~i;i--)
		if((dis>>i)&1)
		{
			tmp=d[x][i]+tmp;
			x=fa[x][i];
		}
	return tmp;
}
ll query(int x,int y)
{
	int t=lca(x,y);
	if(t==y)return up(x,deep[x]-deep[y]+1).s;
	if(t==x)return down(y,deep[y]-deep[x]+1).s;
	return (up(x,deep[x]-deep[t]+1)+down(y,deep[y]-deep[t])).s;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n;i++)
		scanf("%d",&fa[i][0]);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&u[i][0].a);
		d[i][0].a=u[i][0].a;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&u[i][0].b);
		d[i][0].b=u[i][0].b;
	}
	for(int i=1;i<=n;i++)
	{
		deep[i]=deep[fa[i][0]]+1;
		for(int j=1;j<17;j++)
		{
			fa[i][j]=fa[fa[i][j-1]][j-1];
			u[i][j]=u[i][j-1]+u[fa[i][j-1]][j-1];
			d[i][j]=d[fa[i][j-1]][j-1]+d[i][j-1];
		}
	}
	int x,y;
	while(m--)
	{
		scanf("%d%d",&x,&y);
		printf("%lld\n",query(x,y));
	}
    return 0;
}

T3 染色

【问题描述】

一排有n个格子,染成m种颜色,相邻格子颜色不能相同。此外,允许一个长度不超过k的区间染成相同的颜色。求最小代价。

【输入格式】

第一行包含三个正整数n, m, k表示格子数量、颜色数量、区间长度。

接下来的n行,每行有m个正整数cost i,j 表示将第i个格子染成颜色j需要付出的代价。

由于输入数据过大,可能需要使用快速读入

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0'|ch>'9')ch= getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch= getchar ();
    return x;
}

【输出格式】

输出一个整数,表示合法方案的最小代价。

【样例输入】

5 3 3
1 5 6
1 2 3
9 1 9
9 1 9
1 2 3

【样例输出】

6

【样例说明】

颜色分别为1 2 2 2 1

【数据规模与约定】

15% n, m<=10 k<=n

40% n, m<=100 k<=10

60% n, m<=300 k<=n

另15% n, m<=2000 k=1

100% n, m<=2000 k<=n cost<=10000

思路

为什么Day4的我只能看懂一道题

40%:

动态规划

\(k=1\)

\(dp[i][j]\)表示第i个格子染成第j种颜色,前i个格子都染色的最小代价

\(dp[i][j]=min_(j!=j’)dp[i-1][j’]+cost[i][j]\)

\(min_(j!=j’)dp[i-1][j’] = min(min_(j’<j) dp[i-1][j’] , min_(j’>j) dp[i-1][j’] )\)

同时维护\(dp[i][j]\)的前缀最小值和后缀最小值

时间复杂度为\(O(n^2)\),也可以使用数据结构查询区间最小值复杂度多一个log

60%:

枚举相同颜色的区间(l,r)和颜色t,枚举量为O(nmk)

中间的代价已经固定,剩余变成了(1,l-1)和(r+1,n)并且l、r不能染色成t和k=1相同,所以只需要提前预处理前缀dp和后缀dp,O(1)得出代价

时间复杂度O(nmk)

100%:

先枚举颜色t,将上述做法枚举区间进行优化

代价为\(dpL[l-1][t]+Sum[r][t]-Sum[l-1][t]+dp[r][t]=(dpL[l-1][t] -Sum[l-1][t])+(Sum[r][t]+dp[r][t])\) 单调队列优化

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 3005
using namespace std;
int n,m,k,cost[N][N],sum[N][N];
int f[N][N],fl[N][N],fr[N][N];
int g[N][N],gl[N][N],gr[N][N];
int A[N],B[N],q[N],l,r;
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&cost[i][j]);
			sum[i][j]=sum[i-1][j]+cost[i][j];
		}
	memset(f,0x3f,sizeof(f));
	memset(fl,0x3f,sizeof(fl));
	memset(fr,0x3f,sizeof(fr));
	for(int j=1;j<=m;j++)
		f[0][j]=fl[0][j]=fr[0][j]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			f[i][j]=cost[i][j]+min(fl[i-1][j-1],fr[i-1][j+1]);
		for(int j=1;j<=m;j++)
			fl[i][j]=min(f[i][j],fl[i][j-1]);
		for(int j=m;j;j--)
			fr[i][j]=min(f[i][j],fr[i][j+1]);
	}
	memset(g,0x3f,sizeof(g));
	memset(gl,0x3f,sizeof(gl));
	memset(gr,0x3f,sizeof(gr));
	for(int j=1;j<=m;j++)
		g[n+1][j]=gl[n+1][j]=gr[n+1][j]=0;
	for(int i=n;i;i--)
	{
		for(int j=1;j<=m;j++)
			g[i][j]=cost[i][j]+min(gl[i+1][j-1],gr[i+1][j+1]);
		for(int j=1;j<=m;j++)
			gl[i][j]=min(g[i][j],gl[i][j-1]);
		for(int j=m;j;j--)
			gr[i][j]=min(g[i][j],gr[i][j+1]);
	}
	int ans=1e9;
	for(int j=1;j<=m;j++)
	{
		l=1,r=0;
		for(int i=1;i<=n;i++)
		{
			A[i]=sum[i][j]+min(gl[i+1][j-1],gr[i+1][j+1]);
			B[i]=min(fl[i-1][j-1],fr[i-1][j+1])-sum[i-1][j];
		}
		for(int i=1;i<=n;i++)
		{
			while(l<=r&&i-q[l]>=k)l++;
			while(l<=r&&B[q[r]]>=B[i])r--;
			q[++r]=i;
			ans=min(ans,A[i]+B[q[l]]);
		}
	}
	printf("%d\n",ans);
    return 0;
}

T4 数字

【问题描述】

给出n个好数和m个坏数,求包含所有好数但不包含任何坏数,并且只由1-4组成的数中,各位数字之和最小值是多少。(好数和坏数均为k位数,且由1-4组成)

【输入格式】

第一行包含三个正整数n, m, k

接下来n行包含每行1个正整数,表示好数

接下来m行包含每行1个正整数,表示坏数

【输出格式】

输出一个正整数,表示最小的值。输入数据保证一定存在解。

【样例输入】

3 3 4
1111
1222
2333
1122
1133
3122

【样例输出】

20

【样例说明】

12223331111

【数据规模与约定】

20% n<=5 m=0 k=2

30% n<=10 m<=10 k<=5

40% n<=17 m<=20 k<=5

50% n<=18 m<=20 k<=5

60% n<=19 m<=20 k<=5

70% n<=20 m<=20 k<=5

100% n<=20 m<=10000 k<=8

思路

看成不断添加一个新数字

这样相当于好数组成一个哈密顿通路

只需要求出两两好数之间最短距离,然后状压dp求出哈密顿通路

好数之间距离可以使用最短路算法求出,数据范围较小时可能有些其他的求法

由于边权均为1~4,可以拆边使用bfs求最短路,直接最短路可能会被卡时间

8位1-4组成的数可以看作4进制数进行运算,从而节省空间

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
#define pa pair<int,int>
using namespace std;
int n,m,k;
int good[25],bad[10005];
int dis[N][4];
bool vis[N];
int trans(int x)
{
	int y=0;
	for(int i=0;i<k;i++)
	{
		y|=(x%10-1)<<(2*i);
		x/=10;
	}
	return y;
}
queue<pa>q;
void bfs(int x)
{
	memset(dis,0x3f,sizeof(dis));
	if(vis[x])return;
	dis[x][0]=0;
	q.push(pa(x,0));
	while(!q.empty())
	{
		pa x=q.front();
		q.pop();
		int now=dis[x.first][x.second];
		if(x.second)
		{
			if(dis[x.first][x.second-1]>1e9)
			{
				dis[x.first][x.second-1]=now+1;
				q.push(pa(x.first,x.second-1));
			}
			continue;
		}
		for(int i=1;i<=4;i++)
		{
			int y=((x.first<<2)|(i-1))&((1<<(2*k))-1);
			if(dis[y][i-1]>1e9&&!vis[y])
			{
				dis[y][i-1]=now+1;
				q.push(pa(y,i-1));
			}
		}
	}
}
int dist[25][25];
int f[21][1<<20];
int calc(int x)
{
	int y=0;
	for(int i=0;i<k;i++)
	{
		y+=x%10;
		x/=10;
	}
	return y;
}
int main()
{
//	freopen("num8.in","r",stdin);
//	freopen("num2.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&good[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&bad[i]);
		vis[trans(bad[i])]=1;
	}
	for(int i=1;i<=n;i++)
	{
		bfs(trans(good[i]));
		for(int j=1;j<=n;j++)
			dist[i][j]=dis[trans(good[j])][0];
	}
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;i++)
		f[i][1<<(i-1)]=calc(good[i]);
	for(int S=0;S<(1<<n);S++)
	for(int i=1;i<=n;i++)
	if((S&(1<<(i-1))))
	{
		for(int j=1;j<=n;j++)
		if(!(S&(1<<(j-1))))
		f[j][S|(1<<(j-1))]=min(f[j][S|(1<<(j-1))],f[i][S]+dist[i][j]);
	}
/*	int I[25],ii,J[25],jj;
	for(int S=1;S<(1<<n);S++)
	{
		ii=jj=0;
		for(int i=1;i<=n;i++)
			if((S>>(i-1))&1)
			{
				if(f[i][S]<1e9)
					I[++ii]=i;
			}
			else
				J[++jj]=i;
		for(int i=1;i<=ii;i++)
		{
			int iii=I[i];
			for(int j=1;j<=jj;j++)
				f[J[j]][S|(1<<(J[j]-1))]=min(f[J[j]][S|(1<<(J[j]-1))],
					f[iii][S]+dist[iii][J[j]]);
		}	
	}*/
	int ans=1e9;
	for(int i=1;i<=n;i++)
		ans=min(ans,f[i][(1<<n)-1]);
	printf("%d\n",ans);
    return 0;
}

总结

这一篇写得我非常郁闷,总而言之一句话:

什么都不会,什么都得学,特别是图论和DP,然而csp将近,我就只能临阵磨枪,不快也光了,所以,加油吧,胜利一定属于我(我相信吗?

上一篇:java基础day4


下一篇:Nota Day4