嗯为了防止大家AK,所以这次的A题和K题我们就当做不存在好了!
经历了昨天写了两个多小时的博客没保存的心态炸裂,今天终于下了个Markdown。所以我猜这篇的格式应该会更好看一点!
好吧废话不多说
题目链接:Sichuan University Programming Contest 2018 Preliminary
B: 自觉此心无一事,小鱼跳出绿萍中。
Time Limit: 1000 MS Memory Limit: 32726 K
Description
协会某主事者想吃零食,便动起了“歪脑筋”。按照学期计划,协会本已经准备好在哪些天安排Training的讲座,并当场赠送零食。该主事者想将其中至多 M M 次改为训练赛,以便“公饱私囊”(也不能做的太明显,会引起全民公愤)。
基于这样一个事实,两次相邻讲座间的时间跨度越久,主事人能贪得越多,我们就以这段时长作为贪多少的指标。贪心的他想知道,这样一次最少最少能收入多少的零食?(最贪心的做法是,在所有取法中该取法每次贪的最少量最多)
Input
多组输入样例。每组样例包含两行。
第一行包含三个整数,分别表示学期的时长 (0,1,...,L) ( 0 , 1 , . . . , L ) ,预定的讲座数,以及他至多能“取缔”多少次讲座。注意,0时间点和 L L 时间点举办的讲座老师会参加,没有在次讲座内给出,但万万不能更改。
第二行包含个整数 Di D i ,表示第 i i 次讲座本来举办的时间点。保证不会有两个讲座同时举办。
且 M≤N≤500,000 M ≤ N ≤ 500 , 000 0<Di<L 0 < D i < L
Output
每组样例输出一行。
每行只包含一个整数,即一次最少得到个单位时间的零食。
Sample Input
25 5 2
2 11 14 17 21
Sample Output
4
Hint
当取缔2和14后,讲座举办的时间变成了0,11,17,21,25,各次贪的零食的量是11,6,4,4,最少是4,是所有取法中最贪心的。
emmmm比赛的时候这题的题面是改过的,但是虽然改过了,可能还是没说清楚。
简单来说,可以认为每天(不管开不开会)会积累一个单位的零食,然后每次讲座主事者都会贪污,贪污的数目就是从上一次开会为止积累的零食。比如第二天开了第一次讲座,那么贪污得到的零食为2(第1天和第2天),然后第六天又开了一次讲座,那么这次贪污得到的零食为4(第3,4,5,6天)。
现在要把其中的至多M次讲座改为训练赛,而训练赛是没机会贪污零食的,所以零食会继续累积到下一次讲座。现在要使所有贪污中,贪的最少的一次得到的零食最多。
比如样例:本来第二天可以获取2份零食,但是把第二天取缔掉之后,就是第11天贪11份,14天贪3份,17天贪3份,21天贪4份,再在这个基础上把14天取缔掉后,就变成了第11天贪11份,17天贪6份,21天贪4份
题目意思明白之后,既然是求最小的最大,那么我们很自然的想到二分,把求解问题变成判定问题。
既然只有L天,那么贪得的零食范围一定在1到L之间,这就是二分的上下界。
每次二分,我们要验证的是当假设最少贪得的零食为mid时,能不能通过删除至多M次讲座来达到这个效果。这时我们只需要找没有处理过的最早的那次讲座,判断这次讲座离上一次未被取缔的讲座的距离是否超过mid,如果不超过,那么就贪心的把后面的那次讲座给取缔掉,如果取缔的总次数超过m了,那么就说明mid不能满足条件,也就是说总会有比mid贪得更少的情况出现,这时就要令r=mid-1.反之则令l=mid+1.并记录当前结果mid。
题目中还说了,第L天的讲座是不能被取缔的,那怎么处理呢?按照之前的方法,当两次讲座之间的间隔超过mid,我们是取缔了后一次讲座,而在这里,后一次讲座不能取缔的情况下,我们就要考虑取缔前一次未被取缔的讲座。如果要取缔前一次未被取缔的讲座,那我们必须保证那次讲座不为0,因为第0天的讲座也不能被取缔,所以在最后一次判断中,我们要判断一下last记录的讲座是否为0。
用cout会TLE
#include<iostream>
#include<algorithm>
#define up_for(i,l,r) for(int i = l; i < r; i++)
#define maxn 500005
using namespace std;
int a[maxn];//讲座时间
int main() {
int L,n,m;
while(cin>>L>>n>>m) {
a[0] = 0, a[++n] = L;
up_for(i,1,n) cin>>a[i];
sort(a+1,a+n+1);
int l = 1, r = L, ans = 0;
while(l <= r) {
int mid = (l+r)>>1;
int step = m, last = 0;
up_for(i,1,n)//考虑第i场讲座和上一次(last)讲座是否超过mid,超过则取缔下一次讲座,否则更新last
if(a[i] - a[last] < mid) step--;
else last = i;
if(a[n] - a[last] < mid) {//考虑最后一场讲座和上一次讲座是否超过mid,超过则考虑能否取缔last讲座
if(last == 0) {
r = mid-1;
continue;
}
step--;
}
if(step < 0) r = mid-1;//m次取缔仍然会剩余比mid更小的贪污
else l = mid+1, ans = ans > mid ? ans : mid;
}
printf("%d\n",ans);//cout会TLE
}
return 0;
}
C: 西风吹老洞庭波,一夜湘君白发多。
Time Limit: 1000MS Memeory Limit:65536KB
Description
求所有由1-9组成的长度为n的序列逆序对总数。
Input
第1行为输入组数 T T 。
第2—T+1行为长度。
2≤n≤1012,T≤105 2 ≤ n ≤ 10 12 , T ≤ 10 5
答案模上 264 2 64 .
Output
每组样例输出一行。
Sample Input
1
2
Sample Output
36
Hint
比如 n等于2时:
(2,1),(3,1),(3,2),…(9,8)共36个.
题目很好懂,但是却不好想。
首先还是解释一下逆序对,我们一般认为从小到大符合我们的理解规律,即顺序,而从大到小就是逆序。在一个序列中,我们任取两个元素作为一个序列,如果排在前面的元素的值大于后面的,那么这就是一个逆序对。比如在序列4 3 1 5中,(4,1)就构成一个逆序对,(4,3)也构成一个逆序对。而这个序列的逆序对总数为3.
现在我们要考虑的,不仅仅是一个序列有多少个逆序对,而是要考虑长度为n的所有序列有多少逆序对。
长度为n的逆序对,总共有 9n 9 n 种,显然枚举是不可能的。
显然,每个逆序对是独立的,就是说某个逆序对出现的时候,另一个逆序对不可能和他在同一个序列中的相同位置出现。所以对一个单独的逆序对的讨论并不会影响对其他逆序对的计数。我们可以只考虑单独的一个逆序对,考虑它在整体情况中的权重。
这句话确实很难懂,什么意思呢?比方说,我们只考虑(2,1)这组逆序对,只计算这组逆序对的个数。那么在 9n 9 n 个序列中,这个逆序对出现了多少次呢?
我们可以进一步分情况讨论,既然我们已经确定逆序对是(2,1),我们就可以讨论这两个数字出现的位置。我们只考虑有多少种序列,满足2出现在第i位,而1出现在第j位的情况。显然,当我们把2,1以及他们的位置都确定好了之后,其他数是什么与我有什么关系呢?所以(2,1)出现在第i,j位的,长度为n的,由1-9组成的逆序对个数为 9n−2 9 n − 2 个。也就是说,在 9n 9 n 个序列中,有 9n−2 9 n − 2 个序列满足在第i位为2,第j位为1,这些序列在第i,j位对总体逆序数的贡献为 9n−2 9 n − 2 个
刚刚也说了我们是分情况讨论的对吧,所以我们就要讨论到底有多少种情况呢?情况的总数显然就是i,j的个数,也就是在n个元素中选2个的数目,所以这个总数为 C2n C n 2
至此,我们知道了一个逆序对在所有序列中出现的次数,即 C2n×9n−2 C n 2 × 9 n − 2
然后逆序对有多少种情况呢?也就是在1-9这9个数中选2个,即 C29 C 9 2 种。
所以这题到这里就已经全部解决了,即最终的答案为 C29×C2n×9n−2 C 9 2 × C n 2 × 9 n − 2
但是,题目还有一个很严肃的问题:要模 264 2 64 ,我们知道,即便是C语言自带的范围最大的整数类型unsigned long long的取值范围也只能从 0 0 到。那我们怎么处理这个问题呢?
其实,当你知道有符号数和无符号数的编码原则,这个问题就不是问题了。因为unsigned long long炸掉的结果,就刚好是对 264 2 64 取模的结果。
如果你是long long类型的两个变量a,b,那a*b炸long long的话可能会得到一个负数或是不知其然的结果,但是对于unsigned long long,a*b炸掉unsigned long long的结果就刚好是a*b% 264 2 64 的结果
相对于前面的公式推导来说,后面的技巧就显得没那么难了。其实也只用到了一个快速幂的技巧,在这里就不详谈了。
#include<cstdio>
#include<iostream>
using namespace std;
typedef unsigned long long ull;
ull ipow(ull a, ull b)
{
if(b == 0)
return 1;
ull t = ipow(a,b/2);
if(!(b%2)) return t*t;
return t*t*a;
}
int main()
{
int T;
cin>>T;
while(T--)
{
ull n;
cin>>n;
ull ans = 0;
ans = n*(n-1)*ipow(9,n-1)*2;
cout<<ans<<endl;
}
return 0;
}
D: 醉后不知天在水,满船清梦压星河。
Time Limit: 1000MS Memeory Limit:65536KB
Description
在自然数序列 (0,1,2,3,4,5,...) ( 0 , 1 , 2 , 3 , 4 , 5 , . . . ) 中,求去掉所有含2,3,5,7的数字后 (0,1,4,6,8,9,...) ( 0 , 1 , 4 , 6 , 8 , 9 , . . . ) 的第k个数.
Input
第1行为输入组数 T T 。
第2—T+1行为长度。
T≤103,1≤k≤1012 T ≤ 10 3 , 1 ≤ k ≤ 10 12
Output
每组样例输出一行。
Sample Input
3
1
4
1000000000000
Sample Output
0
6
4086441010601686
这题乍一看好像挺难的,但是如果稍微改一下题目,我们就非常容易找到规律。
怎么改呢?我们把删掉2,3,5,7改成删掉6,7,8,9
那我们的自然数序列会变成什么样子呢?
0 1 2 3 4 5
10 11 12 13 14 15
20 21 22 23 24 25
30 31 32 33 34 35
40 41 42 43 44 45
50 51 52 53 54 55
100 101 102 103 104 105
有没有看出来点什么???
对没错,这就是我们熟悉的6进制数!也就是说,从10进制的数符里面删掉4个符号,那么我们的序列就会从10进制变为6进制。
同样的我们也可以简单看一下按照题目删除后得到的自然数序列
0 1 4 6 8 9
10 11 14 16 18 19
40 41 44 46 48 49
60 61 64 66 68 69
80 81 84 86 88 89
90 91 94 96 98 99
和上面的序列相比,我们发现,如果把上面所有的2,3,4,5全部换成4,6,8,9,就刚好符合我们这题的条件!
所以这题想到这里就变成了一个进制转换题。将k转换为6进制,并且把2,3,4,5全部换位4,6,8,9,即是答案。
#include <stdio.h>
#include <iostream>
using namespace std;
char s[50];
typedef long long ll;
int main()
{
int T; scanf("%d", &T);
while(T --)
{
ll k; cin >> k;
k --;
int idx = 0;
do
{
s[idx ++] = '0' + (k % 6);
k /= 6;
}while(k);
for(int i = 0; i < idx; i ++)
{
if(s[i] == '2') s[i] = '4';
else if(s[i] == '3') s[i] = '6';
else if(s[i] == '4') s[i] = '8';
else if(s[i] == '5') s[i] = '9';
}
for(int i = idx-1; i >= 0; i --) printf("%c", s[i]);
if(T > 0) printf("\n");
}
return 0;
}
E: 去年今日此门中,人面桃花相映红。
Time Limit: 1000MS Memeory Limit:65536KB
Description
众所周知,gcd是最大公约数(the greatest common divisor)的意思.
那么给你个 N N 数,那么有多少个四元组
其中 i<j<m<n) i < j < m < n )
满足 gcd(ai,aj,am,an) g c d ( a i , a j , a m , a n ) 为1
Input
第一行 T T 为代表有多少组数据.
每组数据以N开始,代表有多少个数. 4≤N≤104 4 ≤ N ≤ 10 4
接下来一行包括N个数字 a1,a2,...,an a 1 , a 2 , . . . , a n . 1≤ai≤104 1 ≤ a i ≤ 10 4
Output
每组样例输出一行。
Sample Input
3
4
2 4 6 1
5
1 2 4 6 8
10
12 46 100 131 5 6 7 8 9 10
Sample Output
1
4
195
emmmm题目也是乍一看很难然后想到方法直想骂自己傻逼的那种最后真正动手写起来发现根本不会的那种
如果正面考虑(其实看到这句话就知道马上要从反面考虑了)的话,我们很难找到一个很好的办法来处理4个数的gcd。
首先要说这个题不是我写的,所以emmmm我们队的写法我是没特别懂的emmmm,听说用了一个什么特别高逼格的莫比乌斯反演,我也不知道是个什么东西吧。。。
所以在这里只能说最基本的思路,而具体怎么实现的我就无法解释了。
让我们从反面来考虑这个问题:有多少种四元组,使得其最大公约数不为1呢?
或者换一种说法,有多少种四元组,使得其最大公约数为k呢?
这个问题就非常简单了对吧!我们只需要把范围内所有的k的倍数筛出来,假设一共有 nk n k 个,那么我们的四元组个数即为 C4nk C n k 4 。
只要从总的方案数中把最大公约数为k的四元组全都减掉,不就是我们的答案了吗?
但是,我们注意到,按照我们上面提到的方法,是会出错的。为什么呢?因为我们重复减掉了很多东西。比如我们要减掉2的倍数,那么我们可能是在序列2,4,6,8,10,12,14,16中找出4个来,这时候如果找出来的恰好是4,8,12,16,那这几个数的最大公约数为4,这意味着这组情况将在k=4时被减去,而不是在k=2时。
一一验证?显然不是,这会很大程度上提高我们的算法复杂度。面对这种子问题在多个集合中重复出现的情况,我们很容易想到容斥原理。
到了容斥原理之后,我们应该要根据k的质因数个数,来决定到底是加上这个集合的四元组个数,还是减去这个集合中的四元组个数。当k的质因数为奇数个时,我们应该加上其四元组个数,反之,当k的质因数个数为偶数时,我们要减去它。
emmmm思路分析到这里我觉得已经十分到位了,接下来就是如何去实现它了。(不会莫比乌斯,实现略)
#include <stdio.h>
#include <math.h>
#include <string.h>
const int maxn = 1e4 + 10;
typedef long long ll;
int mobi[maxn]={0, 1}, num[maxn], prim[maxn], cnt = 0, a[maxn];
void Init()
{
for(int i = 0; i < maxn; i ++) num[i] = 1;
for(int i = 2; i < maxn; i ++)
{
if(num[i])
{
prim[cnt ++] = i;
mobi[i] = -1;
}
for(int j = 0; j < cnt && prim[j]*i < maxn; j ++)
{
num[prim[j]*i] = 0;
if(i % prim[j] == 0)
{
mobi[prim[j]*i] = 0;
break;
}
mobi[prim[j]*i] = -mobi[i];
}
}
}
void Factor(int n)
{
int s = sqrt(n);
for(int i = 1;i <= s;i++)
if(n % i == 0) a[i] ++, a[n/i] ++;
if(s*s == n) a[s] --;
}
ll Get(ll i)
{
return i*(i-1)*(i-2)*(i-3)/24;
}
int main()
{
Init();
int T; scanf("%d", &T);
while(T --)
{
int n, num;
scanf("%d", &n);
memset(a, 0, sizeof(a));
for(int i = 0; i < n; i ++)
{
scanf("%d", &num);
Factor(num);
}
ll ans = 0;
for(ll i = 1; i < maxn; i ++)
{
if(mobi[i] == 0 || a[i] < 4) continue;
ans += Get(a[i])*mobi[i];
}
printf("%lld\n", ans);
}
return 0;
}
F: 人面不知何处去, 桃花依旧笑春风。
Time Limit: 1000MS Memeory Limit:65536KB
Description
给你一个正整数序列,问其中有多少个严格上升子序列?答案模上1e9+7
Input
第一行为 T T 代表有多少组数据.
每组数据以N开始,代表有多少个数. N≤105 N ≤ 10 5
接下来一行包括N个数字 a1,a2,...,an a 1 , a 2 , . . . , a n . ai a i 在 int i n t 范围内
Output
每组样例输出一行。
Sample Input
3
3
1 1 2
5
1 2 1000 1000 1001
3
1 10 11
Sample Output
5
23
7
如果不看数据范围的话,这题很容易想到dp
因为上升子序列是dp经典问题嘛,设以第i个数结尾的上升子序列个数为dp[i],那么这个子序列有两种来源:只由第i个数组成,或是接在之前以第j个数结尾的子序列之后,当然,前提条件是 ai>aj a i > a j .
这样我们自然而然就可以总结出状态转移方程:
dp[i]=1+∑dp[j],j<i d p [ i ] = 1 + ∑ d p [ j ] , j < i 且 aj<ai a j < a i
但是我们都知道,dp的复杂度是 O(n2) O ( n 2 ) 级别的,对于题目1e5的范围来说,是肯定会T的。
怎么办?其实我们的关键就是找到:第i个数之前,比 ai a i 小的j对应的dp[j]之和。
这时候你有没有想到一个经典例题:求一个序列的逆序对个数?那个题目关键是找到在第i个数之前,比 ai a i 大的数的个数。同样能用 O(n2) O ( n 2 ) 的算法来求解,但是我们可以用树状数组,将其优化到 O(nlogn) O ( n l o g n )
这两个题的共同点是:找到第i个数之前,所有满足某个特定关系的数值之和。
回到这个题目,结合树状数组的思想,如果我们保证,当更新dp[i]时,所有在第i个数之前,比第i个数都大的j对应的值dp[j]都为0,那我们的问题不就变成了求前i项和了吗?我们知道树状数组求前缀和的复杂度为 O(logn) O ( l o g n ) 级别的,这就可以很好的解决这个问题。
但是要保证更新第i个元素对应的值之前,比第i个元素大的元素对应的值都要为0,我们就不能像dp一样从0到n依次更新了,而是要按照数据的大小,从小到大更新。
因此,我们可以对元素进行一份拷贝,将拷贝进行排序,每次更新拷贝中最小的元素对应的值。而更新值对应的操作,就是求一个前缀和s[i]。当然,求前缀和之前,我们要知道i的值,即当前最小元素对应在原数组中的下标值。为了获取这个下标值,我们在这里采取了一个离散化的操作(后来想想好像不离散化,直接将id和值捆绑在一起排序也是可以的,毕竟只需要一个下标就行了)
思路大致就这样,有一个小细节是当有重复元素出现时,因为上升子序列要求严格有序,所以他们不应该有先后关系,而应该共享树状数组中的同一个结点。
#include <stdio.h>
#include <string.h>
#include <map>
#include <algorithm>
typedef long long ll;
using namespace std;
const ll maxn = 1e5 + 100;
const ll mod = 1000000007;
ll C[maxn], a[maxn], aa[maxn];
void Add(ll x, ll d, ll n)
{
while(x <= n)
{
C[x] = (C[x] + d) % mod;
x += (x & -x);
}
}
ll Sum(ll x, ll n)
{
ll ans = 0;
while(x > 0)
{
ans = (ans + C[x]) % mod;
x -= (x & -x);
}
return ans;
}
ll id = 0;
map<ll, ll> mp;
ll GetID(ll n)
{
if(mp[n]) return mp[n];
return mp[n] = ++id;
}
int main()
{
int T; scanf("%d", &T);
while(T --)
{
ll n; scanf("%lld", &n);
for(int i = 0; i < n; i ++)
{
scanf("%lld", &a[i]);
aa[i] = a[i];
}
sort(aa, aa+n);
id = 0;
mp.clear();
memset(C, 0, sizeof(C));
for(int i = 0; i < n; i ++) aa[i] = GetID(aa[i]);
for(int i = 0; i < n; i ++) a[i] = GetID(a[i]);
// for(int i = 0; i < n; i ++) printf("%d ", a[i]);
// printf("\n");
for(int i = 0; i < n; i ++)
{
ll res = Sum(a[i]-1, n) % mod;
Add(a[i], res+1, n);
}
printf("%lld", Sum(aa[n-1], n) % mod);
if(T > 0) printf("\n");
}
return 0;
}
G: 聚散苦匆匆,此恨无穷。今年花胜去年红。
Time Limit: 1000MS Memeory Limit:65536KB
Description
地上有一个长和宽分别为a,b的矩形,然后两位玩家先后依次在这个矩形内画半径为r的圆,所画的圆可以相切但不能相交,直到某个玩家不能再往其中画圆了则判定此玩家失败,另一玩家获胜。
Input
第一行为组数T。
每组输入三个整数 a,b,r a , b , r , (0<a,b,r<1000) ( 0 < a , b , r < 1000 )
Output
每组样例输出一行。
假设两个人都非常聪明,若先画的人赢则输出”The first one is winner.”,否则输出”The second one is winner.”。(不包括引号)
Sample Input
2
4 5 6
5 5 2
Sample Output
The second one is winner.
The first one is winner.
显然是一个博弈题
博弈题要找最佳策略,其实在很多情况下,最佳策略都离不开两个字:对称。因为很多情况下,最优的选择不仅对于我是最优的,对于敌人也是最优的。
就比如最经典的博弈问题:取石子,桌上100个石子,两个人轮流取,最多拿4个最少拿1个,问先拿赢还是后拿赢
这个题目是后者赢,采取的策略是你拿1我就拿4,你拿2我就拿3,你拿3我就拿2,你拿4我就拿1,保证两个人各取一次之后减少的石子数一定为5,那么最后桌上一定只剩5颗石子,前者不论怎样都拿不完。其实也可以看成是一个对称问题,即关于5对称(或者从数轴上来看是关于2.5这个点对称)。
回到我们这个题,这题的对称方法是什么呢?显然就是你画在哪儿,我就关于矩形的正中心画在对称的地方。用直角坐标系距离,你画在(1,1),那我就画在(-1,-1),这样就保证只要你能画,我就一定能画。
而且我们还注意到,矩形中有唯一的一个位置——矩形中心——是没有对称点的。所以如果我是先手,那我就先把这个位置占住,然后从第二步开始,你画在哪儿,我就画在对称的地方。
说到这里就已经非常清楚了。只要先手的人能画下第一个圆,那他就已经赢定了。如果他画不下,那就输定了。
#include<stdio.h>
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int a,b,r;
scanf("%d%d%d",&a,&b,&r);
if(a >= 2*r && b >= 2*r)
printf("The first one is winner.\n");
else
printf("The second one is winner.\n");
}
return 0;
}
H: 可惜明年花更好,知与谁同?
Time Limit: 1000MS Memeory Limit:65536KB
Description
给你一个数字 Q Q ,你的任务是找一个最小的正整数,使得它的数位之积等于 Q Q ,如果不存在,请输出-1.
Input
第1行为输入组数 T T 。
第2—T+1行为数字。
T≤100,0≤Q≤109 T ≤ 100 , 0 ≤ Q ≤ 10 9
Output
每组样例输出一行。
Sample Input
3
1
16
11
Sample Output
1
28
-1
分解数位
首先我们要明确,既然要使得n尽可能小,那么分解出来的数位的位数就要尽可能的少
每个数位的值只可能为1到9其中之一,所以我们从大到小将其尽可能分解为1-9中的数。如果不能分解,那就说明不存在这样的n
比如128我们就要分解为2*8*8.
当时很多人都看一眼以为是质因数,而没考虑到这里并不需要是质数
然后我们要把分解完的数组合成整数n,既然要n尽可能小,那我们的小数就应该放在尽可能高的位置上。
思路清楚之后题目还是很简单的,这里就不赘述了
#include<cstdio>
#include<cstring>
void solve(int n)
{
int cnt[10];
memset(cnt,0,sizeof(cnt));
for(int i = 9; i >= 2; i--)
while(n % i == 0)
{
cnt[i]++;
n /= i;
}
if(n != 1)
printf("-1");
else
for(int i = 2; i <= 9; i++)
while(cnt[i]--)
printf("%d",i);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int Q;
scanf("%d",&Q);
if(Q == 0) printf("10");
else if(Q < 10) printf("%d",Q);
else solve(Q);
if(T > 0)
printf("\n");
}
return 0;
}
I: 劳歌一曲解行舟,红叶青山水急流。
Time Limit: 1000 MS Memory Limit: 32726 K
Description
签到题,附加题,送命题。
请问,去年(2017)我校在各类国家级赛事中斩获了几金几银几铜?
今年,We Also Want You!
Input
无
Output
输出包含两行如题。
Sample Input
无
Sample Output
无
我只说呵呵
#include<stdio.h>
int main()
{
printf("1 4 6\n");
printf("We Also Want You!\n");
return 0;
}
J: 日暮酒醒人已远,满天风雨下西楼。
Time Limit: 1000 MS Memory Limit: 32726 K
Description
协会有一列柜子的奖品,
富可敌国。现在要在其中挑出一些相邻的抽屉,使得里面的奖品恰好能均分给K个童鞋。
Input
多组输入样例。每组样例包含两行。
第一行包含两个整数 n,K n , K ,分别表示抽屉数和童鞋人数。
第二行包含 n n 个整数,表示每个抽屉内的奖品数。
1≤n≤300,000 1 ≤ n ≤ 300 , 000
K,ai K , a i 保证在int范围内。
Output
每组样例输出一行。
每行包含一个整数x,表示选取奖品的方案数。
Sample Input
5 2
1 2 3 4 5
Sample Output
6
这题是要判断数列中的某一段是否满足某个特定性质,可以考虑前缀和
我们可以这么思考这个问题:如果有一段的和是k的倍数,那他们加上同一个数得到的数,一定是关于k同余的。
换句话说,就是如果(i,j]这一段区间内的和为k的倍数,那么 sj−si s j − s i 的结果也一定是k的倍数。
因此我们求出所有的前缀和,再在其中数,有多少个二元组关于k同余。
假设余数为p的前缀和有q个,那么取法的种数显然为 C2q C q 2
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 300005
using namespace std;
typedef long long ll;
ll sum[maxn];//前缀和
ll mod[maxn];//前缀和对k的模
int main()
{
int n,k,t;
while(scanf("%d%d",&n,&k) != EOF)
{
sum[0] = mod[0] = 0;
for(int i = 1; i <= n; i++)
{
scanf("%d",&t);
sum[i] = sum[i-1] + t;
mod[i] = sum[i] % k;
}
sort(mod+1, mod+n+1);//找两个前缀和余数相等的,即为一种方案
long long cnt = 1;//当前余数对应的数字个数
ll ans = 0;
for(int i = 1; i <= n; i++)
{
if(mod[i-1] != mod[i])
{
ans += cnt*(cnt-1)/2;//cnt个数中选2个
cnt = 1;//重置cnt
}
else
cnt++;
}
ans += cnt*(cnt-1)/2;
printf("%lld\n",ans);
}
return 0;
}
K题略
L: 一生负气成今日,四海无人对夕阳。
Time Limit: 1000 MS Memory Limit: 32726 K
Description
协会正在筹备每周Training的奖品。现分别有一大袋辣条和棒棒糖,每个大袋装有若干个小包辣条和棒棒糖。每小包辣条和棒棒糖各有各的“价值”。现在要分别选出若干小包辣条和棒棒糖,为了确保公平性,需满足辣条总价值和棒棒糖相等,求最懒共需要选出多少包?
Input
多组输入样例。每组输入样例包含两行。
第一行以一整数 H H 开头,代表辣条包数。随后紧跟个整数 hi h i ,代表每包辣条的价值。
第二行以一整数 D D 开头,代表棒棒糖包数。随后紧跟个整数 di d i ,代表每包棒棒糖的价值。
H,D≤100 H , D ≤ 100
1≤hi,di≤1,000 1 ≤ h i , d i ≤ 1 , 000
Output
每组样例输出一行。
如果无论怎么选都达不到要求,那么输出“impossible”;否则输出最小需要选出的总包数。
Sample Input
4 10 10 10 10
10 8 8 8 12 12 12 8 8 12 12
4 7 7 14 7
3 11 22 11
Sample Output
4
impossible
这题。。。注意数据范围
H和D 1e2
hi和di 1e3
总共才1e5
考虑选择或不选择的问题
背包无疑了。
每种物品只能选1个
01背包
dp[i]表示凑出i元需要的最小数量(辣条或棒棒糖),如果凑不出i元则为inf
花一个价值为v的物品凑出i元的前提是在用这个物品前已经凑出了i-v元,且凑出i元的最小数量即为凑出i-v元的最小数量+1
故状态转移方程为dp[i] = min(dp[i], dp[i-v]+1)
然后找到dp1[i]+dp2[i]的最小值就行了。
写01背包的时候注意每个元素是从后往前更新的。
#include<cstdio>
#include<algorithm>
#define maxn 105
#define maxd 1005
#define inf 0xffffff
using namespace std;
//dp[i]:凑出价值i所需要的最小数量
int dp1[maxn*maxd], dp2[maxn*maxd];
int main()
{
int H,D;
int h[maxn],d[maxn];
while(scanf("%d",&H) != EOF)
{
for(int i = 1; i < maxn*maxd; i++)
dp1[i] = dp2[i] = inf;
dp1[0] = dp2[0] = 0;
for(int i = 0; i < H; i++)
scanf("%d",&h[i]);
scanf("%d",&D);
for(int i = 0; i < D; i++)
scanf("%d",&d[i]);
for(int i = 0; i < H; i++)//第一个背包
for(int j = maxn*maxd; j >= h[i]; j--)
dp1[j] = min(dp1[j], dp1[j-h[i]]+1);
for(int i = 0; i < D; i++)//第二个背包
for(int j = maxn*maxd; j >= d[i]; j--)
dp2[j] = min(dp2[j], dp2[j-d[i]]+1);
int ans = inf;
for(int i = 1; i < maxn*maxd; i++)
ans = min(ans, dp1[i]+dp2[i]);
if(ans >= inf)
printf("impossible\n");
else
printf("%d\n",ans);
}
return 0;
}
M: A + B Problem
Calculate a + b
Input
The input will consist of a series of pairs of integers a and b, separated by a space, one pair of integers per line.
Output
For each pair of input integers a and b you should output the sum of a and b in one line, and with one line of output for each line in input.
Sample Input
1 2
3 4
5 6
Sample Output
3
7
11
Sample program
#include <stdio.h>
int main()
{
int a, b;
while (scanf("%d%d", &a, &b)==2)
{
printf("%d\n", a+b);
}
return 0;
}
import java.util.Scanner;
public class Main {
public static void main(String args[]) throws Exception {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println(a + b);
}
}
}
PROGRAM p1001;
VAR a,b: Integer;
BEGIN
WHILE NOT EOF(input) DO
BEGIN
ReadLn(a,b);
WriteLn(a+b)
END
END.