文章目录
A C2-妙妙趣和值
题面
定义一个长度为的数组的妙妙趣值是
那么请问对于一个长度为的序列
,所有长度为的子序列
的妙妙趣值和为多少。
某个序列的子序列
是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。
答案为100000007
取模
输入
第一行两个数,表示序列的长度,子序列的长度。
接下来一行,个整数
输出
一行一个数,表示妙妙趣和值
输入样例
4 3
1 7 3 5
输出样例
8
样例解释
长度为3的子序列有 [1,7,3], [1,3,5], [7,3,5], [1,7,5],每一个妙妙趣值都是2
思路
子序列
:某个序列的子序列
是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。即不要求连续,只要求相对位置保持不变。
难题
首先考虑一点,妙妙趣值与子序列的顺序没有关系。
进一步考虑,我们更改原序列的顺序后,对妙妙趣值和没有影响。
所以我们先对原序列进行排序。
然后考虑一个问题,在一个非下降序列中,长度为(注意不是妙妙趣值是)的子序列有多少个。
这个问题是dp可做的。
设是区间里的,长度为,的序列个数。
那么的,其中是满足的最大值。
如何统计答案?
注意是的子序列个数。
那么的子序列个数就是
这里指的是枚举,的值
答案就要加上
更好的方式是,我们只需要枚举x,把dp[n][k]加到答案里即可。
为什么这样正确?
注意dp是子序列个数。
一个子序列如果妙妙趣值为y,那么在枚举x为[1,𝑦]的时候这个子序列都会被统计一次。相当于贡献了y。
这个转化就相对于计算,只需要从后往前算一下前(后)缀和,然后再
那我们枚举x的上界是多少呢?
每个值最大是,如果枚举到,复杂度高达
事实上我们只需要枚举到即可,思考一下为什么
复杂度为,可以认为被越掉,计为
代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long double ld;
typedef long long ll;
const int mod = 100000007;
const int ms = 1005;
int n, k;
int a[ms], dp[ms][ms];
int solve(int x){
dp[0][0] = 1;
int li = 0;
for (int i = 1; i <= n; ++i)
{
while (a[i] - a[li + 1] >= x) li++;
dp[i][0] = dp[i - 1][0];
for (int j = 1; j <= k && j <= i; ++j)
{
dp[i][j] = dp[i - 1][j] + dp[li][j - 1];
dp[i][j] %= mod;
}
}
return dp[n][k];
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
sort(a + 1, a + n + 1);
int ans = 0;
for (int i = 1; i * (k - 1) <= a[n]; i++)
{
ans = ans + solve(i);
ans %= mod;
}
cout << ans;
return 0;
}
B C2-与非门
题面
C C2-Zexal的钢管切割
题面描述
在钢管切割的背景下,已经知道长度为1−n的钢管的价值,给定长度为n的钢管在切割若干次(也可以不切割)所带来的最小价值是?
输入
多组数据输入
第一行一个整数n,为起始钢管长度(0<n≤1000)
第二行n个整数,分别为长度为i的钢管的价值ti(0<ti≤106)
输出
对于每组数据,输出一行,为这根钢管所带来的最小价值T
输入样例
3
2 3 7
输出样例
5
思路
算法导论第三版15.1节。书本例题求最大值,求最小值将max换为min即可
代码
脑残的化成了负值,为了用max思路。。
#include <iostream>
#include <cstdio>
int n;
int t[1010],f[1000001];
int max(int a,int b){
return a>b?a:b;
}
int main() {
while(~scanf("%d",&n)){
f[0]=0;
for(int i=1;i<=n;i++){
scanf("%d",&t[i]);
t[i]=-t[i];
}
for(int i=1;i<=n;i++){
int q=-1000010;
for(int j=1;j<=i;j++){
q=max(q,t[j]+f[i-j]);
}
f[i]=q;
}
printf("%d\n",-f[n]);
}
return 0;
}
D C2-Zexel的流水线问题
题目描述
Zexal的偶像SkyLee趁着假期来逛漫展,漫展只有一条通道,只能从入口进入,出口离开,且左边有n个店铺,右边也有n个店铺,编号都是1~n。为了照顾大家没法兼顾逛两边的痛苦,左边和右边编号相同的店铺卖同样的周边。
现在SkyLee站在入口处的路中间,立志要从头到尾按顺序逛完所有的店铺。
因为每家店铺排队的人数不同,SkyLee在左边第i家要停留 p1[i] 的时间,在右边第i家要停留 p2[i] 的时间。而且这条路还挺宽的,所以从左边第i家移动到右边第i+1家需要t[1][i]的时间;从右边第i家移动到左边第i+1家需要t[2][i]的时间。
由于时间安排的紧,SkyLee想知道逛完漫展至少需要多长时间。
开始时从路中间到任意一边的时间以及同一边相邻店铺移动时间忽略不计。
输入
多组数据输入
对于每一组测试数据,第一行1个数 n (0<=n<=500)表示店铺数 。
接下来一行 n 个数,表示在左边店铺停留时间 p1 。再接下来一行n个数,表示在右边店铺停留时间 p2 (0<=p1,p2<=1000)。
接下来2行,每行n−1个数,分别表示 t[1][i] 和 t[2][i] (0<=t<=300)。
输出
对于每组数据,输出一行,为最少时间T。
输入样例
3
10 1 10
8 5 10
3 1
1 3
输出样例
20
思路
课件例题,车间流水线。注意开始和结束时的时间安排,这题不耗时
代码
#include<stdio.h>
#define MAXN 501
int p1[MAXN],p2[MAXN];//当前店铺停留时间
int t1[MAXN],t2[MAXN];//t1[i]:从左边第i家到右边第i+1家所需的移动时间
int f[2][MAXN];//记录数组
int Min(int a,int b);
int main()
{
int n;//店铺数
while(scanf("%d",&n) == 1){
int i;
for(i = 1;i <= n;i++)
scanf("%d",&p1[i]);
for(i = 1;i <= n;i++)
scanf("%d",&p2[i]);
for(i = 1;i <= n-1;i++)
scanf("%d",&t1[i]);
for(i = 1;i <= n-1;i++)
scanf("%d",&t2[i]);
//初始化
f[0][1] = p1[1];
f[1][1] = p2[1];
//状态转移
int j;
for(j = 2;j <= n;j++){
f[0][j] = Min(f[0][j-1]+p1[j],f[1][j-1]+t2[j-1]+p1[j]);
f[1][j] = Min(f[1][j-1]+p2[j],f[0][j-1]+t1[j-1]+p2[j]);
}
if(f[0][n] >= f[1][n])
printf("%d\n",f[1][n]);
else
printf("%d\n",f[0][n]);
}
}
int Min(int a,int b)
{
if(a < b)
return a;
else
return b;
}
E C2-Zexal的排座位
题目描述
在一个班级中挑选N个学生排成一列座位(保证有足够多的男生与足够多的女生),要求座位序列中男生互不相邻,求解有多少种排列方式?(挑选男生与女生的数量与排列方式均为任意)
例如挑选三个学生,那么所有排列为: 女女女、女男女,男女女,女女男,男女男
输入
第一个数为学生总数N(0<N<30)
输出
只有一行,保证男生与男生不相邻,座位排列的所有情况数目的结果
输入样例
3
输出样例
5
思路
已排好n个座位,第n+1个座位的性别根据第n个座位的性别做出选择,故n+1的座位的排序方法由n个座位的排序方法得来。
代码
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int ms = 50;
ll res[ms][2];
int main(){
res[1][0] = res[1][1] = 1;
for(int i = 2; i <= 30; ++i){
res[i][0] = res[i - 1][0] + res[i - 1][1];
res[i][1] = res[i - 1][0];
}
int n;
while (cin >> n){
cout << res[n][0] + res[n][1] << "\n";
}
return 0;
}
F C2-Zexal的竞赛
题目描述
在一场竞赛中有n道题目,每道题都会对应一个分值,你可以选择任意一道题作答,比如你选择了x分的题目,那么在作答完毕(假设一定可以得分)后,你将获得x分,但是这场比赛中分值等于x−1和x+1的其他题目就会消失,那么这场比赛中你最多可以得到多少分?
输入
第一个数为题目总数
接下来为 个整数
输出
输出你可以得到的最高分
输入样例
9
1 2 1 3 2 2 2 2 3
输出样例
10
样例解释
每次都选择2 选择5次即可得到10分 1和3根据题目要求会消失
思路
当选择第n时,考虑第n-1个是否选了。
这样看是一个经典的dp问题。
if(i == 0) dp[i] = 0;
if(i==1) dp[i]=num[i];
if(i>1) dp[i] = max(dp[n-2]+num[i],dp[n-1])
代码
#include<iostream>
#include<cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int ms = 1e5 + 10;
int cnt[ms], dp[ms][2];
int main()
{
int n, a;
cin >> n;
for (int i = 0; i < n; ++i)
{
cin >> a;
cnt[a]++;
}
for (int i = 1; i < ms; ++i)
{
dp[i][0] = max(dp[i - 1][1], dp[i - 1][0]);
dp[i][1] = dp[i - 1][0] + cnt[i] * i;
}
cout << max(dp[ms - 1][0], dp[ms - 1][1]);
return 0;
}