前言
红太阳的题目真是劲啊233.
T1
English
题目描述
小 Q 作为一名高二党,面临着语数英学业水平考试,但他高一没好好学,非常着急,于是他找到了你,请你来帮助他学习。小 Q 要学的第一门课是英语,可他连字母都不会写,他打听到了一个地方,叫兔子街,他要在这学英语。这条街上有连续的 n 所学校,但是一所学校只会教小 Q 所有小写字母中的一个。 小 Q 只能在连续的一段学校内上学,现在他想写一个单词,所以他请你帮忙,算一下他最少需要在几所学校上学才能写出这个单词。
输入描述
第一行一个数 n,表示有 n 所学校。 接下来一行一个长度为 n 的仅包含小写字母的字符串,表示这 n 所学校分别教哪一个字母。第三行一个长度为 m 仅包含小写字母的字符串,表示小 Q 想要写的单词。 输出一行一个整数,表示小 Q 至少在几所学校上学,才能完整的写出这个单词。
样例输入 1
7
aabbbcc
abc
样例输出 1
5
样例输入 2
11
apllaeelpce
apple
样例输出 2
5
样例解释 1
由于小 Q 只能在一段连续的学校内上学,所以他可以在第 2~6 学校学
习,才能学到 abc 三个字母。
样例解释 2
小 Q 可以选择第 2~6 学校或 5~9 学校,都可以学到 a p l e 四个字母,
从而写出 apple 这个单词。
数据范围
对于 20%数据,n,m<=200;
对于 50%数据,n,m<=1000;
对于 100%数据,n,m<=100,0000;
数据保证存在一段连续的学校,使得小 Q 能够学会整个单词。
看完题先搞出来暴力,看完t2,,t3后又回来看了下,发现答案符合单调性,于是开始二分乱搞,算了算虽然是100w但二分常数贼小应该卡不住233,事实证明加个手读比O(n)算法快pwq。考完听神犇们说可以尺取法O(n)搞,恍然大悟233,不过分一样还好啦。
代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
int x = 0 , f = 1;
char in = getchar();
while(in < '0' || in > '9')
{
if(in == '-')
f = -1;
in = getchar();
}
while(in >= '0' && in <= '9')
{
x = (x << 3) + (x << 1) + in - '0';
in = getchar();
}
return x * f;
}
int n;
char in[1000010];
char ins[1000010];
bool vis[101];
int use[101];
bool check()
{
for(int i = 0 ; i < 26 ; i ++)
if(vis[i] && !use[i])
return false;
return true;
}
bool check_mid(int mid)
{
memset(use,0,sizeof(use));
for(int i = 0 ; i < mid ; i ++)
use[in[i]-'a'] ++;
if(check())
{
return true;
}
for(int i = mid ; i <= n ; i ++)
{
use[in[i]-'a'] ++;
use[in[i-mid]-'a'] --;
if(check())
{
return true;
}
}
return false;
}
int main()
{
freopen("English.in","r",stdin);
freopen("English.out","w",stdout);
n = read();
scanf("%s%s",in,ins);
int len = strlen(ins);
for(int i = 0 ; i < len ; i ++)
vis[ins[i]-'a'] = 1;
int l = 1 , r = n;
while(r - l > 1)
{
int mid = l + r >> 1;
if(check_mid(mid))
r = mid;
else
l = mid;
}
if(check_mid(l))
printf("%d\n",l);
else
printf("%d\n",r);
fclose(stdin);
fclose(stdout);
return 0;
}
/*
7
aabbbcc
abc
*/
T2
Chinese.pas/c/cpp
题目描述
小 Q 非常高兴你能够帮他学英语,现在他需要学习语文。首先他需要收集钢笔。 在兔子街上有连续的 n 家商店,小 Q 想要收集所有 m 支笔,每一支笔会在一个时刻 ci 出现在 di 这一家商店里。定义一支笔的损耗为在商店中的时间,也就是被买走的时刻减去被进货的时刻。 小 Q 召唤了 p 位同学帮他收集, p 位同学聚集在 1 号商店门口,由于同学们非常开心,所以当一位同学出发后,就不会中途停下来,但是你可以决定他们应该什么时候出发(可以在时刻 0 之前出发),同学们每走 1 单位距离,需要 1 单位时间,可以认为买笔是不消耗时间的。现在给你这 n家商店之间的距离,求所有笔的损耗加起来最小是多少。
输入描述
第一行三个正整数 n,m 和 p,表示商店的数量,笔的数量,同学的数量。
第二行 n-1 个正整数,表示第 i 家商店与第 i+1 家商店的距离(di<=10^4)。 接下来 m 行,每行两个正整数 ai,ci,表示第 i 支笔被 ai这一商店在 ci 时刻进货。(ai<=ni<=10^9)
输出描述
一行一个整数,表示所有笔的最小损耗值之和。
输入样例
4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12
输出样例
3
样例解释
有 4 家商店,6 支笔,2 位同学。 第一位同学在时刻 0 从 1 号商店出发收集第一支笔,在时刻 1 时到达 2 号商店收集第二支笔,在时刻 9到达 4 号商店,收集第三支笔,损耗均为 0;第二位同学在时刻 10 从1 号商店出发收集第 4 支笔,在时刻 11 到达 2 号商店收集第 5 支笔,损耗为 1,在时刻 14 到达 3 号商店收集第 6 支笔,损耗为 2。总消耗为 3;这是总损耗最小的方案。
数据范围
对于 20%数据,p=1;
对于另外 30%数据,n,m,p<=7;
对于 100%数据 n,m<=500; p<=50;
考试结束前半小时把到每个笔的时间的数组输出了下突然发现是个划分dp,赶紧码了一个,然而并没有什么用233,dp[m][p] 是前 m 支笔用 p 个同学去拿,转移方程见代码
代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int size = 1010;
ll read()
{
ll x = 0 , f = 1;
char in = getchar();
while(in < '0' || in > '9')
{
if(in == '-')
f = -1;
in = getchar();
}
while(in >= '0' && in <= '9')
{
x = x * 10 + in - '0';
in = getchar();
}
return x * f;
}
struct apple_pen
{
ll p,a,c;
}pen[size];
ll n,m,p,num[size],sum[size];
ll pen_time[size];
ll sum_time[size];
ll f[501][70];
int main()
{
freopen("Chinese.in","r",stdin);
freopen("Chinese.out","w",stdout);
n = read() , m = read() , p = read();
for(int i = 1 ; i < n ; i ++)
num[i] = read() , sum[i] = sum[i-1] + num[i];
for(int i = 1 ; i <= m ; i ++)
pen[i].p = i , pen[i].a = read() , pen[i].c = read();
int ans = 0;
for(int i = 1 ; i <= m ; i ++)
pen_time[i] = pen[i].c-sum[pen[i].a-1];
sort(pen_time+1,pen_time+1+m);
for(int i = 1 ; i <= m ; i ++)
sum_time[i] = sum_time[i-1] + pen_time[i];
for(int i = 1 ; i <= m ; i ++)
for(int j = 1 ; j <= p ; j ++)
f[i][j] = 21474836471111ll;
for(int i = 1 ; i <= m ; i ++)
f[i][1] = i * pen_time[i] - sum_time[i];
for(int i = 1 ; i <= m ; i ++)
for(int j = 2 ; j <= p ; j ++)
for(int k = 1 ; k < i ; k ++)
f[i][j] = min(f[i][j],f[k][j-1] + (i-k)*pen_time[i] - sum_time[i] + sum_time[k]);
printf("%lld\n",f[m][p]);
fclose(stdin);
fclose(stdout);
return 0;
}
/*
3 3 1
1 3
1 0
2 1
3 3
4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12
*/
T3
Math.pas/c/cpp
题目描述
小 Q 终于来到了最后一部分——数学。他想要去找数学老师给自己辅导功课,数学老师为了考察他的智商,给他出了一道难题。老师给小 Q一个坐标系,上面有 N 个点,每个点的坐标为非负整数。现在老师想要让小 Q 恰好删掉其中 k 个点,使得剩下的点两两之间距离的最大值尽量小。于是小 Q 找到了你,请你帮忙解决这一问题。两点间距离=sqrt((x1-x2)²+(y1-y2)²)
输入描述
第一行两个正整数 n,k,分别代表点的数量和需要删掉点的数量。
接下来 n 行, 每行两个整数, 描述一个点的坐标,这些点按照给出顺序进行标号,编号为 1~n。
输出描述
一行 k 个数,表示需要删掉点的编号。如果有多组解,输出任意一组。
输入样例 1 输入样例 2
5 2 4 1
1 2 0 0
0 0 0 0
2 2 1 1
1 1 1 1
3 3
输出样例 1 输出样例 2
2 5 2
样例解释 1
删除 2 号点与 5 号点后,最大距离为 3 号与 4 号点距离≈1.41421
样例解释 2
删除任意一点后,两点之间的最大值都≈1.41421,所以输出任意一点
都正确。
数据范围
N<=1000 K<=30
虽然答案符合单调性,但还是是个np问题,正解是爆搜加减枝,貌似减枝后的复杂度是可以证明与fib相关的,不是很懂。