T1:佳肴
Description
佳肴就是非常美味的菜的意思,佳肴最关键的是选择好原料。
现在有N种原料,每种原料都有酸度S和苦度B两个属性,当选择多种原料时,总酸度为每种原料的酸度之积,总苦度为每种原料的苦度之和。
正如大家所知,佳肴是既不酸也不苦的,因为要保证所选的原料使得总酸度和总苦度差的绝对值最小。
由于佳肴不能只有水,所以必须至少选择一种佳肴。
Input
输入第一行包含一个整数N(1<=N<=10),表示原料的种数。
接下来N行每行包含两个用一个空格隔开的整数,分别表示酸度和苦度。
输入数据保证如果所有原料都选上,总酸度和总苦度不会超过10^9。
Output
输出总酸度和总苦度最小的差。
Sample Input
输入1:1 3 10
输入2:
2 3 8 5 8
输入3:
4 1 7 2 6 3 8 4 9
Sample Output
输出1:7
输出2:
1
输出3:
1
Hint
【样例解释】
样例3中选择最后三种原料,这样总酸度为2×3×4=24,总苦度为6+8+9=23,差为1。
简要思路:就这小的可怜的数据范围,直接模拟即可,不过要注意特判是否一种佳肴都未选上。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int s[11] , b[11];
int n , ans;
void dfs( int pos , int ss , int bb , int flag ) {
if ( pos == n + 1 ) {
if(!flag) {
return;
}
int t = abs( ss - bb );
ans = min( ans , t );
return;
}
dfs( pos + 1 , ss * s[pos] , bb + b[pos] , 1 );
dfs( pos + 1 , ss , bb , flag );
}
int main () {
scanf("%d",&n);
for ( int i = 1 ; i <= n ; ++i ) {
scanf("%d",&s[i]);
scanf("%d",&b[i]);
}
ans = 0x3f3f3f3f;
dfs( 1 , 1 , 0 , 0 );
printf("%d",ans);
return 0;
}
T2:取数游戏
Description
Alice想让Bob陪他去看《唐山大地震》,但由于Bob是个很感性的人,怕流泪不想去,但又不好意思以这个作为拒绝的理由,便提出玩一个游戏。
N个正整数围成一圈,规则如下:
•两个玩家轮流取数;
•最开始先手的玩家可以取任意一个数x;
•从第二步开始当前玩家只能取x(上一玩家刚刚取的数)左右两边相邻的数;
•直到取完所有的数,游戏结束;
•取得较多奇数的玩家获胜。
Bob为了显示大度,让Alice先取,但他忘了自己和Alice都是绝顶聪明之人,现在Alice请你帮他计算第一步有多少种取法使得最终获得胜利。
Input
第一行包含一个整数N(1<=N<=100),表示数的个数。第二行包含N个正整数,每个数都在1到1000之间,任意两个数互不相同。
Output
输出Alice第一步有多少种取法。
Sample Input
输入1:3 3 1 5
输入2:
4 1 2 3 4
输入3:
8 4 10 5 2 9 8 1 7
Sample Output
输出1:3
输出2:
2
输出3:
5
简要思路:这道题是一道非常典型的假的 “博弈题”,正解还是区间DP。我们用
记录区间
内奇数的个数(我的预处理方式可以靠画图来理解),用
来表示区间
内最多可以取到多少个奇数,转移方程为
,因为本题要处理环,Alice的取胜方案即为不同的剪环方式,当剪出的区间中总的奇数个数比这个区间减去一个格子后的最多能取得的奇数个数的两倍还多时(绕口令 ),答案加一。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int f[205][205];
int sum[205][205];
int num[205];
int n;
void read( int & res ) {
res = 0;
int pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
int main () {
read(n);
int x;
for ( int i = 1 ; i <= n ; ++i ) {
read(x);
num[i] = num[i + n] = x;
sum[i][i] = sum[i + n][i + n] = num[i] % 2;
f[i][i] = f[i + n][i + n] = sum[i][i];
}
for ( int i = ( n << 1 ) ; i >= 1 ; --i ) {
for ( int j = i - 1 ; j >= 1 ; --j ) {
sum[j][i] = sum[j][j] + sum[j + 1][i];
}
}
int ans = 0;
for ( int k = 1 ; k <= n ; ++k ) {
for ( int i = k + n - 1 ; i >= k + 1 ; --i ) {
for ( int j = i + 1 ; j <= k + n - 1 ; ++j ) {
f[i][j] = sum[i][j] - min( f[i + 1][j] , f[i][j - 1] );
}
}
if ( sum[k + 1][k + n] - f[k + 1][k + n - 1] > f[k + 1][k + n - 1] ) {
ans++;
}
}
printf("%d",ans);
return 0;
}
T3:删除
Description
Alice上化学课时又分心了,他首先画了一个3行N列的表格,然后把数字1到N填入表格的第一行,保证每个数只出现一次,另外两行他也填入数字1到N,但不限制每个数字的出现次数。
Alice现在想删除若干列使得每一行排完序后完全一样,编程计算最少需要删除多少列。
Input
第一行包含一个整数N(1<=N<=100000),表示表格的列数。
接下来三行每行包含N个整数,每个数在1到N之间,而且第一行的数互不相同。
Output
输出最少需要删除的列数。
Sample Input
输入1:7 5 4 3 2 1 6 7 5 5 1 1 3 4 7 3 7 1 4 5 6 2
输入2:
9 1 3 5 9 8 6 2 4 7 2 1 5 6 4 9 3 4 7 3 5 1 9 8 6 2 8 7
Sample Output
输出1:4
输出2:
2
Hint
【样例解释】
例1中Alice需要删除第2、4、6、7这四列,然后每行排完序都是1、3、5。
【数据范围】
40%的数据N<=100
70%的数据N<=10000
简要思路:这题其实是一道简单的模拟题,厘清题意,不难发现,第一行每个数都出现一次,第二三行不一定每个数都有,我们要删除的是第二行或第三行没有的数但第一行有的数所对应的列,删除后记得第二三行对应的数也会被删,可能又会缺一些数,这个操作重复进行,直到第一行没有第二三行没有的数。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e5 + 5;
int n;
int a[N] , b[N] , c[N];
int fb[N] , fc[N];
void read( int & res ) {
res = 0;
int pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
int main () {
read(n);
for ( int i = 1 ; i <= n ; ++i ) {
read(a[i]);
}
for ( int i = 1 ; i <= n ; ++i ) {
read(b[i]);
fb[b[i]]++;
}
for ( int i = 1 ; i <= n ; ++i ) {
read(c[i]);
fc[c[i]]++;
}
int ans = 0;
int flag = 0;
while ( !flag ) {
flag = 1;
for ( int i = 1 ; i <= n ; ++i ) {
if ( a[i] && ( fb[a[i]] == 0 || fc[a[i]] == 0 ) ) {
ans++;
a[i] = 0;
flag = 0;
fb[b[i]]--;
fc[c[i]]--;
}
}
}
printf("%d",ans);
}
T4:区间
Description
Alice收到一些很特别的生日礼物:区间。即使很无聊,Alice还是能想出关于区间的很多游戏,其中一个是,Alice从中选出最长的不同区间的序列,其中满足每个区间必须在礼物中,另序列中每个区间必须包含下一个区间。
编程计算最长序列的长度。
Input
输入文件第一行包含一个整数N(1<=N<=100000),表示区间的个数。
接下来N行,每行两个整数A和B描述一个区间(1<=A<=B<=1000000)。
Output
输出满足条件的序列的最大长度。
Sample Input
输入1:3 3 4 2 5 1 6
输入2:
5 10 30 20 40 30 50 10 60 30 40
输入3:
6 1 4 1 5 1 6 1 7 2 5 3 5
Sample Output
输出1:3
输出2:
3
输出3:
5
Hint
【样例解释】
例3中可以找到长度为5的区间序列是:[1,7]、[1,6]、[1,5]、[2,5]、[3,5]
简要思路:将区间左端点定为 ,右端点定为 ,将所有区间按以 从小到大为第一关键字,以 从大到小为第二关键字进行排序。因此按照顺序搜索时 一定递增,此时只要找到最长的 递减的序列就是最终答案,实现考虑二分寻找LIS。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
int q[N];
int n;
struct node {
int x;
int y;
}str[N];
void read( int & res ) {
res = 0;
int pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
bool cmp( node a , node b ) {
if ( a.x != b.x ) {
return a.x < b.x;
} else {
return a.y > b.y;
}
}
int main () {
read(n);
for ( int i = 1 ; i <= n ; ++i ) {
read(str[i].x);
read(str[i].y);
}
int ans = 0;
sort( str + 1 , str + 1 + n , cmp );
for ( int i = 1 ; i <= n ; ++i ) {
int l = 1 , r = i;
int cur = str[i].y;
while ( l <= r ) {
int mid = ( l + r ) >> 1;
if ( q[mid] >= cur ) {
l = mid + 1;
} else {
r = mid - 1;
}
}
if ( q[l] < cur ) {
q[l] = cur;
}
ans = max( ans , l );
}
printf("%d",ans);
return 0;
}