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 想表示的是路由器覆盖区间的最右端的房间。
第二种则表示的是路由器房间及其覆盖区间右端的房间。
第二种更加巧妙。