Codeforces Round #587 (Div. 3) 题解(A~F)

A. Prefixes    暴力

给出一个偶数长度的字符串,只包含a和b, 要求每个偶数长度的前缀都包含相同数量的a和b

每两位判断一次,如果a和b数量不相同,就将a变为b 或者将b变为a

#include <stdio.h>
#include <iostream>
using namespace std;
char s[2*100005],c;
int main(){
	int n;
	cin>>n;
	int cnt = 0, j = 0;
	int sum = 0, sum1 = 0, sum2 = 0; //分别表示每两位a,b的数量, 和操作总数
	getchar();
	while((c=getchar())!=EOF){
		if(c=='\n') break;
		s[j++] = c; 
		if(c=='a') sum++,cnt++, (cnt & 1 ? cnt = cnt :( sum==sum1 ? sum = 0,sum1 = 0 : (sum = 0,sum1 = 0,s[j-1] = 'b', sum2++))); 
		else sum1++, cnt++, (cnt & 1 ? cnt = cnt : (sum==sum1 ? sum = 0,sum1 = 0 : (sum = 0,sum1 = 0,s[j-1] = 'a', sum2++))); 
	}
	cout<<sum2<<endl<<s;
	return 0;
}

B.

间接排序。

给出n个罐子,罐子的耐久值为wi,你要将n个罐子全部击倒,可以改变击倒罐子的顺序,击倒罐子所花费代价的公式为

(w0 * 0 + 1) + ( w1 * 1 + 1)+(w2 * 2 + 1)+ .....+(wi *(i - 1)+ 1)  

求出最少花费代价,并输出击倒罐子的顺序

不直接排序wi数组,创建另一个b 数组保存wi数组的下标,根据wi数组的值降序排序b数组。

#include <iostream>
#include <stdio.h>
#include <algorithm> 
int a[1010];
int b[1010];
using namespace std;
bool cmp(const int i, const int j){return a[i] > a[j];} 
int main(){
	int n;
	cin>>n;
	for(int i = 0; i < n; i++){
		cin>>a[i];
		b[i] = i;
	}
	sort(b,b+n,cmp);
	int sum = 1;
	for(int i = 1; i < n; i++){
		sum+=a[b[i]]*i+1;
	}
	cout<<sum<<endl;
	for(int i = 0; i < n; i++){
		i > 0 ? cout<<" "<<b[i] + 1 : cout<<b[i]+1;
	}
	return 0;
} 

C. White Sheet

判断两个黑色的长方形是否完全覆盖白色长方形。

完全覆盖分两种情况

1. 有一个黑色长方形能直接覆盖白色长方形。即黑色长方形左下角顶点的 x 要小于白色长方形左下角顶点的 x,右上角顶点的x要大于白色长方形的x , y值同理。

2.两个黑色长方形分别覆盖白色长方形的左边一部分和右边一部分 或者 覆盖白色长方形的上边一部分和下边一部分。

 看图:

以红色方框代表白色长方形。 黑色方框代表两个黑色长方形

第一种

第二种 

#include <stdio.h>
#include <iostream>
using namespace std;
struct node{
	int x,y;
};
node A[10];
int Pan(node A, node B, node C, node D){  // 某个完全包含   C,D 是白色长方形的两个顶点
	if(A.x <= C.x && B.x >= D.x && A.y <= C.y && B.y >= D.y)
		return 1;
	return 0;
}
int Pan1(int x1, int x2, int x3, int x4, int x5, int x6, int y1, int y2, int y3, int y4,int y5, int y6){ //各一半 
	if(x3 <= x1 && x4 > x1 && x5 <= x4 && x6 >= x2 && y3 <= y1 && y5 <= y1 && y4 >= y2 && y6 >= y2)
		return 1;
	return 0;
}
int main(){
	for(int i = 1; i <= 6; i++){
		cin>>A[i].x>>A[i].y;
	}
	if(Pan(A[3],A[4],A[1],A[2])||Pan(A[5],A[6],A[1],A[2])||Pan1(A[1].x, A[2].x, A[3].x, A[4].x, A[5].x, A[6].x,A[1].y, A[2].y, A[3].y, A[4].y, A[5].y, A[6].y)||Pan1(A[1].x, A[2].x, A[5].x, A[6].x, A[3].x, A[4].x,A[1].y, A[2].y, A[3].y, A[4].y, A[5].y, A[6].y)||Pan1(A[1].y, A[2].y, A[3].y, A[4].y, A[5].y, A[6].y,A[1].x, A[2].x, A[3].x, A[4].x, A[5].x, A[6].x)||Pan1(A[1].y, A[2].y, A[5].y, A[6].y, A[3].y, A[4].y,A[1].x, A[2].x, A[3].x, A[4].x, A[5].x, A[6].x))
	{cout<<"NO"; return 0;}
	else cout<<"YES";
	return 0;
}

D. Swords

因为每个类型的剑的总数未知,所以以取走一部分剑后中的最大值作为初始时每个类型的剑的总数。

假设最大值为 max, 一共有n种剑,剩余的剑的总数为 s_sum   则被取走了 max * n - s_sum 。

最开始想从1开始枚举, 用被取走的剑数分别除以1,2,3.....直到找个合适的。但发现这样好像并不太可行。...

因为每个人只能拿一类剑,而且拿的剑数相同,所以每个类型的剑中被拿走的剑的数量一定有一个相同的最因数.

假如每个人拿 x 把, 第一种类型会被拿走 i1 * x 把, 第二种会被拿走 i2 * x把,第三种会被拿走 i3 * x 把......

所以我们只要求出每个类型的剑被拿走的剑数的最大公因数,用被拿走的剑的总数除以每个人拿的剑数就能求出人数。

#include <stdio.h>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
int a[2*100005];
int main(){
	int n;
	cin >> n;
	int max = -1;
	unsigned long long sum = 0; 
	for(int i = 0; i < n; i++){
		cin>>a[i];
		if(max<a[i]) max = a[i];
		sum+=a[i];
	}
	sum =  max*1ll*n - sum;
	ull ai0 = max - a[0];
	for(int i = 1; i < n; i++){
		ull ai1 = max - a[i];  // 被拿走的剑的数量
		ai0 && ai1 ? ai0 = __gcd(ai0,ai1) : ( !ai0 ? ai0 = ai1: (!ai1 ? ai0 = ai0 : ai1 = ai1));//如果是0的话直接跳过
	}
	cout << sum/ai0 <<" "<<ai0;
	return 0;
}

algorithm 中的__gcd()是封装好的呦.

E.Numerical Sequence 

给出一个字符串 形如 11212312341234512345612345671234567812345678912345678910 求第K位 是什么数字?

我们将其按规律拆分 1 12 123 1234 12345 123456 1234567 12345678 123456789 12345678910(最后一个字符段为11位)

拆分后一眼就能看出规律了.

然后将按规律分割后每个字符串定义为字符段.

首先, 第k位的数字,一定出现在某个字符段 i 中,每个字符段的结尾数字同时也是该段的标号。

那么我们只要知道了前 i - 1 段的长度总和, 在用 k - sum(i-1)   我们就得到了第k 位数字 在第  i 段中的位置。

那么怎么得到 i 和  前 i - 1 段长度总和呢?    

我们通过分析字符段的长度发现,

字符段 1 ~ 9  是首项a0为1 公差d 为 1 ,项数为9的等差数列, 

字符段 10 ~ 99 是首项a0为 11 公差为 2, 项数为90的等差数列  

....

我们又可以将其按规律分割为不同的等差数列

因为当字符段从10到99 每个字段都增加一个两位数, 所以长度增加了 2 所以 公差 为 2  如果是 100 ~ 999 则 公差为 3

所以从第二个等差数列开始  每个数列的首项 为 前一个首项的尾项  + d + 1

即10^0 ~ 10^1 - 1 分为一段    10^1 ~ 10^2 -1分为一段.每一段的公差是前一段的公差 + 1

以sum表示前 i -1 段的长度总和,   

假如 x = i -1  = 115        则先计算标号为10^0 ~ 10^1 内字符段长度总和. 令 a0 = 1,  d = 1,   用 n 表示 项数, m 表示在第几个等差数列

因为 10^(p-1) ~ 10^p - 1 为一段  所以每一段的项数为   10^p - 10^p/10 - 1 + 1    所以 令 m  = 10^p       n = m - m/10   

即  sum += a0 * n + n*(n - 1)/2*d;    

然后 计算 10^1 ~ 10^2 -1中的字符段长度总和

其中 a0 更新为上一个等差数列的尾项加上一段的公差 + 1  即加上此等差数列的公差

a0 = a0 + (n-1) * d + d + 1; (方便理解特意按公式写开) 

然后sum的操作.

之后计算100~115中的字符段的长度总和    项数 n = x - m/10  (减去已经算过的)

然后计算sum.   我们已经知道了计算前 i - 1 个字符段的方法, 随后用 二分来查找出 i. 

用k - sum 计算出第k位数字是第i个字符段中第几个数字后就可以模拟构造该字段。从而查找出该数字。

#include <stdio.h>
#include <iostream>
#include <string>
#include <math.h> 
using namespace std;
typedef unsigned long long ull;
ull get_l(ull x){   // 计算 sum[i]   从1到第i个的总长度 
	ull m = 1, a0 = 1, d = 0, n = 0;  // a0 为首项, d 为当前等差数列的公差   小于10  d = 1, 大于等于10小于100 d = 2, 以此类推 
	ull sum = 0;
	while(1){
		m *= 10, d++; // 每次计算 10的i-1次方 ~ 10的i次方 - 1。 
		n = m - m / 10;
		if(x > m - 1){ // 当x大于10^i时, 求出 10^(i-1) - 10^(i) -1 的长度   如  123  求出 1~9 的长度 在加上 10 ~ 99 的长度 
			sum += a0 * n + n*(n - 1)/2*d;
			a0 = a0 + (n - 1) * d + d + 1;		 // 每一个等差数列的公差为 前一个的尾项 + 公差 + 1; 
		}
		else{   // 当 x 小于 10^i 时, 求出 10^(i-1) - x  的长度   100 ~ 123  
			n = x - m / 10 + 1; // 
			sum += a0 * n + n*(n-1)/2*d;   
			break;
		} 
	}          //  112123123412345123456123456712345678123456789123456789101234567891011  假如 ssum[i] < n; 则答案一定在 i+1 中 
	return sum;        
}
int main(){
	ull n;
	cin>>n;
	while(n--){
		ull a;
		cin>>a;
		int l = 0, r = 1e9, mid = 0, ansi;
		ull ssum; 
		while(l<=r){
			mid = (l + r) >> 1;
			ssum = get_l(mid);
			if(ssum < a) ansi = mid, l = mid + 1;
			else r = mid - 1;  
		}
		a=a-get_l(ansi);        // 在第 ans + 1 段  中第 a 个数字。
		ull x = 1, cnt = 0, len = 0, n = 0, i = 1, yushu = 0, sum = 0;
		while(a){
			x*=10;
			n = x - x / 10;
			len++;
			if(a > n*len){
				sum += n;
				a -= n * len;
			}
			else {
				sum += a/len;// 9 + 90*2 + 333*3 + 1  = 999 + 189 =  1189     333 + 99 = 432  432 + 1 = 433 
				yushu = a % len;
				break; 	
			}
		}
		if(!yushu) cout <<sum%10<<endl;
		else{
			sum += 1;
			while(len!=yushu){
				len--;
				sum/=10;
			}
			cout<<sum%10<<endl;
		}
	}
	return 0;
}

F. WIFI

线性DP+单调队列优化

单调队列能够保存特定区间内的最大值或者最小值。如此一来在查找最小值或者最大值时的时间复杂度就降为了o(1)


求使n个房间联网的最小花费。看着像是dp..

dp[i] 表示前 i 个房间联网的最小花费。

怎么dp, 有两种选择。  当前房间为 0 时 不能安装路由器,不考虑被其他路由器覆盖的情况,最小花费为 i。

当前房间为 1 时, 可以安装路由器,不考虑被其他路由器覆盖的情况,最小发费为 i 。

也就是判断 当前房间为 1 时, 装路由器 与不装路由器谁的花费更小。

因为装了路由器之后影响的房间范围是 [i-k,i+k] 所以被覆盖的房间的花费都为0,所以应该从区间最右端的点开始考虑,最右端的点的最小花费才只能被i点所影响,因为 i - 1 覆盖不到 i + k, i + 1 的花费又大于了 i 。

所以设 j 为最右端的点。状态转移方程就是 dp[j] = min(dp[j], min(dp[j-2*k-1]~dp[j-2*k-1+k]) + j - k);

 min(dp[j-2*k-1] ~ dp[j-2*k-1+k]) 是前j-2*k-1个房间的最小花费,因为第j-2*k-1个房间会可能被其后面的k个房间中的wifi所覆盖,即最小值受后面的影响。

如果暴力搜索这个区间的话就会超时,但我们求的是某个区间的最小值,所以我们可以用单调队列进行优化。

#include <stdio.h>
#include <cstring>
#include <iostream>
#include <deque> 
using namespace std;
typedef long long ll;
const int maxn = 2*1e5+5;
ll dp[2*maxn];
int main(){
	int n,k;
	cin>>n>>k;
	char s[maxn];					 		
	scanf("%s",s+1);
	memset(dp, 0, sizeof dp);
	deque <int> d;
	d.push_back(0); 
	for(int i = 1; i <= n + k; i++){//必须考虑区间最右端的房间.
		dp[i] = dp[i-1] + i;
		if(i- k > 0&&s[i-k] == '1' ){           
			while(!d.empty()&&d.front() < i - 2*k - 1) d.pop_front();
			dp[i] = min(dp[i], dp[d.front()] + i - k);
		}
		while(!d.empty()&&dp[d.back()] >= dp[i]) d.pop_back();    //获取区间最小值。 如果dp[j] > dp[i], j < i 这说明第j个房间被后面的路由器覆盖。      
			d.push_back(i);                                   
	}
	ll m = 0x3f3f3f3f3f3f3f3f;  //最大值超过了int              
	for(int i = n; i <= n + k; i++){  //   可能出现   001101   或者  001100  这两种情况,所以最优解并不一定是dp[n]但一定出现在 dp[n] 到 dp[n+k]之间的. 
			m = min(dp[i],m);
	}      
	cout<<m;
	return 0;                       
}

线性dp + 预处理(贪心)。

#include <stdio.h>
#include <cstring>
#include <iostream>
#include <deque> 
using namespace std;
typedef long long ll;
const int maxn = 2*1e5+5;
ll dp[2*maxn]; 
int ans[maxn];
int main(){
	int n,k;
	cin>>n>>k;
	char s[maxn];					 		
	scanf("%s",s+1);
	memset(dp, 0, sizeof dp);
	ans[n+1] = 0x3f3f3f3f;
	for(int i = n; i >= 1; i--){
		ans[i] = s[i] == '1' ? i : ans[i+1];    // 保存 i 右侧距离 i 最近的点  是为了确保路由器覆盖区间右端的房间花费最小。 
	}
	for(int i = 1; i <= n; i++){
		dp[i] = dp[i-1] + i;
		int l = ans[max(1,i-k)];//查找  i-k ~ i 之间是否有路由器。   
		if(l<=i+k){ // 有。   
			dp[i] = min(dp[i],dp[max(1,l-k)-1]+l);  //还是从区间右端开始考虑    是否在 [i-k,i] 区间内有路由器。如果有的话进行比较
		}
//		cout<<dp[i]<<endl;
	} 
	cout<<dp[n];
	return 0;                       
}

两种方法中  第一种的 i 想表示的是路由器覆盖区间的最右端的房间。

第二种则表示的是路由器房间及其覆盖区间右端的房间。   

第二种更加巧妙。

发布了52 篇原创文章 · 获赞 114 · 访问量 6009

猜你喜欢

转载自blog.csdn.net/GD_ONE/article/details/101172706