AtCoder Beginner Contest 258 A-G

总结

本场感觉EF挺有意思。

A - When?

const int maxn = 1e5 + 10;
typedef pair <int, int> PII;
int main()
{
	int k;
	scanf("%d", &k);
	int m = 21 * 60;
	m += k;
	printf("%02d:%02d\n", m / 60, m % 60);
	return 0;
}

B - Number Box

void cal(int x, int y)
{
		for(int j = 0 ; j < 8 ; j ++)
		{
			ll temp = 0;
			for(int i = 0 ; i < N ; i ++)
			{
				int cur_x = (x + i * dx[j] + N) % N;
				int cur_y = (y + i * dy[j] + N) % N; 
				temp = temp * 10ll + mm[cur_x][cur_y] * 1ll;
			}
			maxx = max(maxx, temp);
		}
}
int main()
{
	
	scanf("%d", &N);
	for(int i = 0 ; i < N ; i ++)
	{
		for(int j = 0 ; j < N ; j ++)
		{
			scanf("%1d", &mm[i][j]); 
		}
	}
	for(int i = 0 ; i < N ; i ++)
	{
		for(int j = 0 ; j < N ; j ++)
		{
			cal(i, j);
		}
	}
	printf("%lld\n", maxx);
	return 0;
}

C - Rotation

题意:

给定一个字符串S,给定Q个查询。
1 x:把最后x个字符移到S前面。
2 x:输出此时第x个字符。
2 < = N < = 5 e 5 , 1 < = Q < = 5 e 5 , 1 < = x < = N 2 <= N <= 5e5, 1<= Q <= 5e5, 1 <= x <= N 2<=N<=5e5,1<=Q<=5e5,1<=x<=N

思路:

根据数据范围可知暴力铁T。
操纵1相当于把此时字符串的开头换成了len - x % len。
那么从现在开始第x个字符为str[len - x % len + x - 1]

int main()
{
	int N, Q;
	scanf("%d %d", &N, &Q);
	string str;
	cin >> str;
	int len = str.length();
	ll sum = 0; 
	while(Q --)
	{
		int t, x;
		scanf("%d %d", &t, &x);
		if(t == 1)
		{
			sum += x * 1ll;
		}
		else
		{
			int sta = len - sum % len;
			x --;
			cout << str[(sta + x) % len] << endl;
		}
	}
	return 0;
}

D - Trophy

题意:

给定N个关卡,对于第一次通关需要A[i],B[i]的时间,非第一次通关,A[i]可跳过。问通过X次需要的最少时间。
1 < = N < = 2 e 5 , 1 < = A [ i ] , B [ i ] < = 1 e 9 , 1 < = X < = 1 e 9 1 <= N <= 2e5, 1<= A[i], B[i] <= 1e9,1 <= X <= 1e9 1<=N<=2e5,1<=A[i],B[i]<=1e9,1<=X<=1e9

思路:

观察数据范围直接暴力铁T。
考虑假设最后第X次通关在关卡a,那么最优策略为[1, a]全是第一次通过,且在第a关重复通关即可。

ll sum[maxn], A[maxn], B[maxn];
int main()
{
	int N, X;
	scanf("%d %d", &N, &X);	
	for(int i = 1 ; i <= N ; i ++)
	{
		scanf("%lld %lld", &A[i], &B[i]); 
	}
	ll ans = 2e18, sum = 0;
	for(int i = 1 ; i <= N ; i ++)
	{
		sum += A[i] + B[i];
		if(i >= X)
		ans = min(ans, sum);
		else
		{
			ans = min(ans, sum + (X - i) * B[i]);
		}
	}
	printf("%lld\n", ans);
	return 0;
}

E - Packing Potatoes

题意:

有无数多土豆且给定权值序列0–N - 1,第i个土豆的权值为W[(i - 1) % N],最开始为1个空箱子,假设此时装进去的土豆权值>=X时,需要换一个新箱子。给定Q个查询,求第K个箱子中装了几个土豆。
1 < = N , Q < = 2 e 5 , 1 < = X < = 1 e 9 1 <= N, Q <= 2e5, 1 <= X <= 1e9 1<=N,Q<=2e5,1<=X<=1e9
1 < = w [ i ] < = 1 e 9 , 1 < = K [ i ] < = 1 e 12 1 <= w[i] <= 1e9,1 <= K[i] <= 1e12 1<=w[i]<=1e9,1<=K[i]<=1e12

思路:

考虑到K达到了1e12,肯定是找循环节。但是这里提供一种不需要找循环节的倍增解法。(qwq 可能是太懒了。)
题目要求出第K个箱子装多少个土豆。(且K特别大)
考虑倍增。
f[i][j].first:从i开始拿 2 j 2 ^ j 2j个箱子的下一次开始拿的下标
f[i][j].second:从i开始拿 2 j 2 ^ j 2j个箱子且第 2 j 2 ^ j 2j个箱子中的值。
f [ i ] [ j ] . f i r s t = f [ f [ i ] [ j − 1 ] . f i r s t ] [ j − 1 ] . f i r s t f[i][j].first = f[f[i][j - 1].first][j - 1].first f[i][j].first=f[f[i][j1].first][j1].first
f [ i ] [ j ] . s e c o n d = f [ f [ i ] [ j − 1 ] . f i r s t ] [ j − 1 ] . s e c o n d f[i][j].second = f[f[i][j - 1].first][j - 1].second f[i][j].second=f[f[i][j1].first][j1].second
可以提前预处理出f[i][0]。
那么每次查询时,只需要根据K的二进制位开始即可。
总的时间复杂度为O(nlogn + Qlogk)) 纯纯的暴力
但是需要注意预处理时出现特殊情况。二分第一个 >= X % sum。
要找到下一次开始拿的下标。
假设此时下标为 i i i且X % sum != 0, 说明此时下一次开始拿的下标为l + 1。
X % sum == 0, 说明下一次开始拿的下标为 i i i

ll w[maxn], sum[maxn], X, t, s;
PII f[maxn][55];
ll N, Q;
bool check(int l, int mid)
{
	if(l == 0)
	{
		if(sum[mid] >= s) return true;
		else return false;
	}
	else 
	{
		if(sum[mid] - sum[l - 1] >= s) return true;
		else return false;
	}
}
void init()
{
	t = X / sum[N - 1];
	s = X % sum[N - 1]; //剩下0个 
	if(s == 0)
	{
		for(int i = 0 ; i < N ; i ++)
		f[i][0].first = i, f[i][0].second = N * 1ll * t;
	}
	else
	{
		for(int i = 0 ; i < N ; i ++)
		{
			int l = i , r = 2 * N - 1;
			while(l < r)
			{
				int mid = (l + r) >> 1;
				if(check(i,mid)) r = mid;
				else l = mid + 1;
			} // l
			f[i][0].first = (l + 1) % N;
			f[i][0].second = N * 1ll * t + l * 1ll - i * 1ll + 1;
		}
	}
}	
void ST()
{
	init();
	for(int j = 1 ; j <= 50 ; j ++)
	{
		for(int i = 0 ; i < N ; i ++)
		{ 
			f[i][j].first = f[f[i][j - 1].first][j - 1].first % N;
			f[i][j].second = f[f[i][j - 1].first][j - 1].second;
		}
	}
}
ll query(ll k)
{
	ll cur = 0, cur_w = 0;
	for(ll i = 50 ; i >= 0 ; i --)
	{
		if(k >> i & 1)
		{
			k -= 1ll << i;
			cur_w = f[cur][i].second;
			cur = f[cur][i].first;
		}
	}
	return cur_w;	
}
int main()
{
	scanf("%lld %lld %lld", &N ,&Q, &X);
	for(int i = 0 ; i < N ; i ++)
	{
		scanf("%lld", &w[i]);
	}
	for(int i = N ; i < 2 * N ; i ++)
	w[i] = w[i - N];
	for(int i = 0 ; i < 2 * N ; i ++)
	{
		if(i == 0)
		sum[i] = w[i];
		else sum[i] = sum[i - 1] + w[i];
	}
	ST();
	while(Q --)
	{
		ll k;
		scanf("%lld", &k);
		printf("%lld\n", query(k));
	}
	return 0;
}

F - Main Street

题意:

在X-Y轴上,在x = n 和 y = n上有普通道路。在x = B * n 和 y = B * n上有高速道路。(n为整数)
对于当前点(x,y),每次可向当前上下左右四个方向移动。在高速道路上移动每步花费1s,否则花费Ks。
给定T组,求从(Sx, Sy)到(Gx, Gy)的最小时间。
1 < = T < = 2 e 5 , 1 < = B , K < = 1 e 9 1 <= T <= 2e5, 1 <= B , K <= 1e9 1<=T<=2e5,1<=B,K<=1e9
0 < = S x , S y , G x , G y < = 1 e 9 0 <= Sx, Sy, Gx, Gy <= 1e9 0<=Sx,Sy,Gx,Gy<=1e9

思路:

观察到范围较大。
要么是贪心,要么是建图最短路。
假设贪心。
对于在两个格子中的点且没有挨着。
S要到达G点。 考虑到最小时间
S肯定是要先走到高速道路上。那么对于G点,别的点要想到达,那么最优的选择(尽量走高速道路)也只有四个,从G号点的上下左右到达。
AtCoder Beginner Contest 258 A-G_第1张图片
如果格子紧挨着。还是一样的。
AtCoder Beginner Contest 258 A-G_第2张图片
考虑同一个格子。和上图一样
最优策略肯定是从4个方向上。
那么总的路径方案为 4 * 4种。
接下来只要需要计算在高速路上的两点之间的需要的最小时间。
AtCoder Beginner Contest 258 A-G_第3张图片
可以发现存在cal()函数中三种方案。取min即可。

PII s[10], g[10];ll b, k, sx, sy, gx, gy;
void func(PII a[], ll x, ll y)
{
	a[1] = {x, y + b - (y % b)}; //上 
	a[2] = {x, y - (y % b)};    // 下 
	a[3] = {x - (x % b), y};    // 左 
	a[4] = {x + b - (x % b), y}; // 右 
}
ll cal(ll x1, ll y1, ll x2, ll y2)
{
	ll temp = 2e18;
	bool flog = false;
	if(x1 / b == x2 / b && y1 % b == 0 && y2 % b == 0)
	flog = true, temp = min({temp, abs(y1 - y2) * k + abs(x1 - x2), abs(y1 - y2) + b - x1 % b + b - x2 % b, abs(y1 - y2) + x1 % b + x2 % b});
	if(y1 / b == y2 / b && x1 % b == 0 && x2 % b == 0)
	flog = true,temp = min({temp, abs(x1 - x2) * k + abs(y1 - y2), abs(x1 - x2) + y1 % b + y2 % b, abs(x1 - x2) + b - y1 % b + b - y2 % b});
	if(!flog) temp = min(temp, abs(x1 - x2) + abs(y1 - y2));
	return temp;
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		
		scanf("%lld %lld %lld %lld %lld %lld", &b, &k, &sx, &sy, &gx, &gy);
		func(s, sx, sy), func(g, gx, gy);
		ll res = (abs(sx - gx) + abs(sy - gy)) * k;
		for(int i = 1 ; i <= 4 ; i ++)
		{
			for(int j = 1 ; j <= 4 ; j ++)
			{
				res = min(res, (abs(s[i].first - sx) + abs(s[i].second - sy) + abs(g[j].first - gx) + abs(g[j].second - gy)) * k + cal(s[i].first, s[i].second, g[j].first, g[j].second));
			}
		}
		printf("%lld\n",res);
	}
	return 0;
}

G - Triangle

题意:

给定一个简单无向图,N * N的邻接矩阵,求有多少个(i, j, k),(i < j < k <= N)
A[i][j] = 1, A[j][k] = 1, A[i][k] = 1
1 < = N < = 3000 1 <= N <= 3000 1<=N<=3000

思路:

暴力思路直接暴力三重for。
枚举i,j ,k。是否满足条件。 O(N ^ 3) 铁T。
考虑优化。
假设A[i][j]=1,那么找A[i][k] = 1且A[j][k] = 1即可。
没有感觉到特别像bitset嘛
用bitset优化一重for。 O(N ^ 3 / 32)

int A[5010][5010];
bitset <5010> f[maxn];
int main()
{
	int N; // [i, j]  [j, k]  [i, k]
	scanf("%d", &N);
	for(int i = 1 ; i <= N ; i ++)
	{
		for(int j = 1 ; j <= N ; j ++)
		scanf("%1d", &A[i][j]), f[i][j] = A[i][j];
	}
	ll res = 0;
	for(int i = 1 ; i <= N ; i ++)
	{
		for(int j = i + 1 ; j <= N ; j ++)
		{
			if(f[i][j])
			{	
				res += (f[i] & f[j]).count();
			}
		}
	}
	printf("%lld\n",res / 3);
	return 0;
}

你可能感兴趣的:(算法,图论,c++)