S2考前综合刷题营Day4

油箱

【问题描述】

\(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≤5,m≤20\)
\(40\%\) 的数据 \(n≤50m≤200\)
\(60\%\) 的数据 \(n≤5×10^2,m≤2×10^3\)
\(100\%\) 的数据 \(n≤5×10^4,m≤2×10^9\)。保证 \(1≤z≤10^9\)

Solution

100pts

二分答案,问题变成判断一个有向图是否任意两点联通。
相当于判断点 \(1\) 是否能到达所有点并且所有点都能到达点 \(1\)
可以建立正向图和反向图,并从点 \(1\) 开始 \(dfs\) 遍历。
也可以直接上 \(tarjan\) 来判断是否连通。
时间复杂度 \(O(n\log{n})\)

#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)
	{
		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);
}

考场上我是用的排序+贪心的思路过的。使图连通的最大边权最小,我们可以很自然的想到最小生成树。
但是最小生成树算法是基于图是没有环的,当若干个点已经连通时,再在它们之间连边显然不优。
所以说,添加一条边的意义一定是使始点的出度或终点的入度减一。我们再加边的时候加上这一步特判即可。
时间复杂度 \(O(n\log{n})\)

#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
const int N=3e5;
int n,m,ans,cnt;
int in[N],out[N];
struct node
{
	int from,to,dis;
}a[N];
bool cmp(node x,node y)
{
	return x.dis<y.dis;
}
int main()
{
	//freopen("tank_ex.in","r",stdin);
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&a[i].from,&a[i].to,&a[i].dis);
	sort(a+1,a+1+m,cmp);
	ans=2*n;
	for(int i=1;i<=m;i++)
	{
		int u=a[i].from;
		int v=a[i].to;
		if(u==v) continue;
		if(!in[v]||!out[u]) 
		{
			cnt++;
			ans-=(in[v]==0)+(out[u]==0);
			out[u]++;
			in[v]++;
		}
		if(ans<=0) 
		{
			printf("%d\n",a[i].dis);
			return 0;
		}
	}
	printf("-1\n");
	return 0;
}

求和

【问题描述】

给定一颗 \(n\) 个点组成的树,根节点为 \(1\) 号节点,每个点上有两个权值 \(a_i, b_i\) 。共有 \(m\) 次询问,每次询问给出两个正整数 \(x, y\),从 \(x\)\(y\) 的路径设为 \(t_1,t_2,…,t_k\)。要求输出

\(\sum_{1≤i<j≤k}a_{t_i}∗b_{t_j}\)

【输入格式】

第一行两个正整数 \(n,m\)

第二行 \(n-1\) 个数 \(f_2,f_3,…,f_n\),表示点 \(i\) 的父亲 \((f_i< i)\)

接下来两行,每行 \(n\) 个数 分别表示 \(a_i,b_i\)

接下来 \(m\) 行,每行两个正整数 \(x_i,y_i\) ,表示第 \(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<=100\)

\(40\% n,m<=2000\)

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

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

\(100\% n,m<=100000\) 保证 \(1<=a_i,b_i<=10000\)

Solution

20pts

纯暴力,\(O(n^2)\) 求路径贡献。
时间复杂度 \(O(n^2m)\)

40pts

我们可以将 \(\sum_{i<j}a_{t_i}*b_{t_j}\) 看作是枚举 \((i,j)\) 这个有序数对,这样的话每次询问可以看作是在 \(1~k\) 区间中找所有的有序数对,然后乘积就和。
考虑如何将 \([1,y]\) 的答案扩展到 \([1,y+1]\)。发现后者的答案比前者多了 \(b_{y+1}*\sum_{1<=i<=y}a_i\),所以我们可以 \(O(1)\) 扩展。
时间复杂度 \(O(nm)\)

60pts

式子变成了 \(\sum_{i<j}a_{i}*a_{j}\)
发现此时 \(i\)\(j\) 的大小顺序其实不影响,结果都是 \(a_{i}*a_{j}\)
所以我们可以调换 \(i,j\)\(\sum_{i>j}a_i*a_j\),得到与上式相等的式子。
我们将两式相加:\(2A=\sum_{i!=j}a_i*a_j=\sum_{i,j}a_i*a_j-\sum_{i=j}a_i*a_j=\sum_{i,j}a_i*a_j-\sum_i{a_i}^2\)
我们将式子展开就可以得到:\(A=\frac{(\sum_ia_i)^2-\sum_i(a_i)^2}{2}\)
所以我们只需要维护路径和和路径平方和即可,利用 LCA 和树上差分 \(O(1)\) 求答案。
时间复杂度 \(O(n\log{n})\)

80pts

\(40pts\) 的扩展方法还是太慢,我们需要进行优化。
树上路径是可以分成两段的,现在我们有了两段的答案,考虑怎么求得大区间的答案。
左区间的答案是 \(\sum_{1<=i<j<=t}a_i*b_j\),右区间的答案是 \(\sum_{t<i<j<=x}a_i*b_j\),大区间的答案是:\(\sum_{1<=i<j<=x}a_i*b_j\)
还是将这个过程看作是枚举点对,左区间的答案相当于两个点都在左边的答案,右区间的答案相当于两个点都在右边的答案,缺少的正好是一个点在左区间,一个点在右区间的答案 \(\sum_{1<=i<=t<j<=x}a_i*b_j\)
\(\sum_{1<=i<=t<j<=x}a_i*b_j=\sum_{1<=i<=t}a_i*\sum_{t<j<=x}b_j\)

100pts

将上述做法扩展到树上即可。\(ans[x]+ans[y]-2*ans[lca]\)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
inline ll read()
{
	char ch=getchar();ll a=0;
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') a=a*10+(ch^48),ch=getchar();
	return a;
}
const ll N=1e5+5;
ll n,m,Edge;
ll a[N],b[N],head[N],dep[N],f[N][21];
ll Sa[N],Sb[N],Ans1[N],Ans2[N];
struct node
{
	ll to,nxt;
}e[N<<1];
void add(ll from,ll  to)
{
	Edge++;
	e[Edge].to=to;
	e[Edge].nxt=head[from];
	head[from]=Edge;
}
void dfs(ll u,ll fa)
{
	dep[u]=dep[fa]+1;
	Sa[u]=Sa[fa]+a[u];              
	Sb[u]=Sb[fa]+b[u];
	Ans1[u]=Ans1[fa]+b[u]*Sa[fa];    //Ans1[u]表示1~u的路径的答案 
	Ans2[u]=Ans2[fa]+a[u]*Sb[fa];    //Ans2[u]表示u~1的路径的答案 
	for(ll i=head[u];i;i=e[i].nxt)
	{
		ll v=e[i].to;
		dfs(v,u);
	}
}
ll LCA(ll x,ll y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(ll i=20;i>=0;i--)
	{
		if(dep[f[x][i]]>=dep[y]) 
		    x=f[x][i];
	}
	if(x==y) return x;
	for(ll i=20;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
int main()
{
    n=read();m=read();
    for(ll i=2;i<=n;i++)
    {
    	f[i][0]=read();
    	add(f[i][0],i);
	}
	for(ll j=1;j<=20;j++)
	    for(ll i=1;i<=n;i++)
	        f[i][j]=f[f[i][j-1]][j-1];
	for(ll i=1;i<=n;i++)
	{
		a[i]=read();
	}
	for(ll i=1;i<=n;i++)
	{
		b[i]=read();
	}
	dfs(1,0);
	for(ll i=1;i<=m;i++)
	{
		ll x=read();
		ll y=read();
		ll lca=LCA(x,y);
		ll ans1=Ans2[x]-Ans2[lca]-(Sa[x]-Sa[lca])*Sb[lca];  //算出x~son[lca]的答案 
		ll ans2=Ans1[y]-Ans1[f[lca][0]]-Sa[f[lca][0]]*(Sb[y]-Sb[f[lca][0]]);  //算出lca~y的答案 
		printf("%lld\n",ans1+ans2+(Sa[x]-Sa[lca])*(Sb[y]-Sb[f[lca][0]])); //合并两区间 
	}
	return 0;
}

染色

【问题描述】

一排有 \(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\)

Solution

40pts

考虑用动态规划来解决。
先考虑 \(k=1\) 的情况:
\(dp[i][j]\) 表示第 \(i\) 的格子染了第 \(j\) 种颜色,前 \(i\) 个格子都染色的最小代价。
\(dp[i][j]=\min(dp[i][j],dp[i-1][j']+cost[i][j])(j≠j')\)
\(j'\) 分成小于 \(j\) 和大于 \(j\) 的两部分,所以有:
\(dp[i-1][j']=\min(\min(dp[i-1][j'](j'<j)),\min(dp[i-1][j'](j'>j)))\)
同时维护 \(dp[i][j]\) 的前缀最小值和后缀最小值。
时间复杂度 \(O(n^2)\)

60pts

考虑 \(k≠1\) 的情况。
我们可以枚举区间 \([l,r]\) 和颜色 \(t\),枚举量 \(O(nmk)\)
中间的代价已经固定,剩余变成了 \([1,l-1]\)\([r+1,n]\),并且 \(l,r\) 不能染成 \(t\)
\(k=1\) 的情况相同,所以只需要提前预处理前缀\(dp\) 和后缀\(dp\)\(O(1)\) 得出代价。
时间复杂度 \(O(nmk)\)

100pts

先枚举颜色 \(t\),将上述做法枚举区间进行优化。
代价为 \(dpL[l-1][t]+Sum[r][t]-Sum[l-1][t]+dpR[r][t]\)
\(=(dpL[l-1][t] -Sum[l-1][t])+(Sum[r][t]+dpR[r][t])\)
\(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]\)
然后可以单调队列优化。

#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);
}

数字

【问题描述】

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

【输入格式】

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

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

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

【输出格式】

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

【样例输入】

3 3 4
1111
1222
2333
1122
1133
3122
111

【样例输出】

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\)

Solution

看成不断添加一个新数字>
这样相当于好数组成一个哈密顿通路,只需要求出两两好数之间最短距离,然后状压 \(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()
{
	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 ans=1e9;
	for(int i=1;i<=n;i++)
		ans=min(ans,f[i][(1<<n)-1]);
	printf("%d\n",ans);
}
posted @ 2020-10-04 20:37  暗い之殇  阅读(156)  评论(0编辑  收藏  举报