T1 奇怪的教室
题目背景
LSU 的老师有个奇怪的教室,同学们会从左到右坐成一个横排,并且同一个位置可以坐多个同学。这天,入学考试的成绩下来了。同学们想根据入学考试的成绩,找出班里学霸扎堆的区域"学霸区"。
题目描述
共有 N N N 个同学。给定每个同学的座位号 X i X_i Xi,以及入学考试的分数 S i S_i Si。多个同学的座位号可能相同。现在给定"学霸区"长度 L L L,请找出所有长度为 L L L 的座位号区间,考试分数之和的最大值。
输入格式
第一行两个整数 N , L N,L N,L,表示学生数量和"学霸区"长度。
接下来 N N N 行,每行两个整数 X i X_i Xi 和 S i S_i Si,表示学生座位号和分数。
输出格式
一个数,表示所有长度为 L L L 的座位号区间,考试分数之和的最大值。
样例 #1
样例输入 #1
6 3
1 2
2 4
3 8
4 4
5 2
1000 1
样例输出 #1
16
提示
样例说明:
选择长度为 3 3 3 的座位号区间 [ 2 , 4 ] [2,4] [2,4],则包含 3 3 3 位同学,其座位号和分数分别为:
2 4
3 8
4 4
分数之和为 4 + 8 + 4 = 16 4+8+4=16 4+8+4=16,最大。
数据范围说明:
对于 10 % 10\% 10% 的数据, L = 0 L=0 L=0(没有边缘);
对于 40 % 40\% 40% 的数据, L ≤ 1000 L\leq 1000 L≤1000;
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1 \leq N\leq 10 ^ 5 1≤N≤105, 0 ≤ L ≤ 1 0 5 0 \leq L\leq 10 ^ 5 0≤L≤105, 1 ≤ X i ≤ 1 0 5 1 \leq X_i\leq 10 ^ 5 1≤Xi≤105, 1 ≤ S i ≤ 100 1\leq S_i\leq 100 1≤Si≤100。
除 L = 0 L=0 L=0 的情况外, L L L 均为 ≥ 3 \geq 3 ≥3 的奇数。
坑点 & 正解
坑点:同一个座位上能做很多人,就能"叠高高"。例如这个样例:
5 1
1 100
1 100
1 100
1 100
2 100
选 1 1 1 这个座位能拿 400 400 400 分。
正解:前缀和。
时间复杂度: O ( max ( X i ) ) O(\max(X_i)) O(max(Xi))。
cpp
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int n,m;
int mx;
const int N = 100010;
int a[N],b[N];
int ans;
void solve()
{
scanf("%d%d",&n,&m);
for (int i = 1;i <= n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x] += y;
mx = max(mx,x); //最大的座位号
}
//前缀和
for (int i = 1;i <= mx;i++)
b[i] = b[i-1] + a[i];
for (int i = 1;i <= mx-m+1;i++)
ans = max(ans,b[i+m-1]-b[i-1]);
cout << ans;
}
signed main()
{
int TTT;
// cin >> TTT;
TTT = 1;
while (TTT--) solve();
return 0;
}
T2 安排班级
题目背景
新学期开始了,LSU 的老师迎来了若干个学生。现在老师要给大家分配班级。
题目描述
只考虑分配给每个班的学生人数。把总共 M M M 个同学分配到总共 N N N 个班里,可以有 0 0 0 个同学的班。请问,一共有多少种不同的分配方案?
注意:假设有 4 4 4 个学生, 3 3 3 个班。 [ 1 , 1 , 2 ] [1,1,2] [1,1,2], [ 1 , 2 , 1 ] [1,2,1] [1,2,1] 和 [ 2 , 1 , 1 ] [2,1,1] [2,1,1] 均视为相同的分配方案。 [ 0 , 2 , 2 ] [0,2,2] [0,2,2] 和 [ 2 , 1 , 1 ] [2,1,1] [2,1,1] 视为不同的分配方案。
输入格式
包含多组测试数据。
第一行为一个整数,表示测试数据的数目 T T T。
接下来 T T T行,每行均包括二个整数 M M M 和 N N N。
输出格式
共 T T T 行,为对每组测试数据的结果。
样例 #1
样例输入 #1
1
7 3
样例输出 #1
8
样例 #2
样例输入 #2
3
3 2
4 3
2 7
样例输出 #2
2
4
2
提示
样例1说明 :
8 8 8 种方案分别为: [ 0 , 0 , 7 ] , [ 0 , 1 , 6 ] , [ 0 , 2 , 5 ] , [ 0 , 3 , 4 ] , [ 1 , 1 , 5 ] , [ 1 , 2 , 4 ] , [ 1 , 3 , 3 ] , [ 2 , 2 , 3 ] [0,0,7],[0,1,6],[0,2,5],[0,3,4],[1,1,5],[1,2,4],[1,3,3],[2,2,3] [0,0,7],[0,1,6],[0,2,5],[0,3,4],[1,1,5],[1,2,4],[1,3,3],[2,2,3]。
数据范围 :
对于所有数据,保证: 1 ≤ M , N ≤ 10 1\leq M,N\leq 10 1≤M,N≤10, 0 ≤ T ≤ 20 0 \leq T \leq 20 0≤T≤20。
坑点 & 正解
坑点:可以有 0 0 0 个同学的班!
正解:一看数据范围就知道是 DFS 暴搜。注意,因为不能分相同的分配方案,所以需要非严格递增去安排人数。
时间复杂度: O ( N ! ) O(N!) O(N!)。
老师的正解:打表。
时间复杂度: O ( 1 ) O(1) O(1),完美过关!
赛时正解:
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int sum;
int a[30];
int m,n;
void dfs(int s,int dep,int last)
{
if (dep == n-1)
{
sum++;
return ;
}
for (int i = last;i <= s/(n-dep);i++)
{
a[dep] = i;
dfs(s-i,dep+1,i);
}
}
void solve()
{
scanf("%lld%lld",&m,&n);
dfs(m,0,0);
printf("%lld\n",sum);
sum = 0;
}
signed main()
{
int TTT;
scanf("%lld",&TTT);
// TTT = 1;
while (TTT--) solve();
return 0;
}
我做的:
cpp
#include <bits/stdc++.h>
//#define int long long
using namespace std;
void solve()
{
int m,n;
cin >> m >> n;
if (m == 1)
{
if (n == 1) cout << 1;
if (n == 2) cout << 1;
if (n == 3) cout << 1;
if (n == 4) cout << 1;
if (n == 5) cout << 1;
if (n == 6) cout << 1;
if (n == 7) cout << 1;
if (n == 8) cout << 1;
if (n == 9) cout << 1;
if (n == 10) cout << 1;
}
if (m == 2)
{
if (n == 1) cout << 1;
if (n == 2) cout << 2;
if (n == 3) cout << 2;
if (n == 4) cout << 2;
if (n == 5) cout << 2;
if (n == 6) cout << 2;
if (n == 7) cout << 2;
if (n == 8) cout << 2;
if (n == 9) cout << 2;
if (n == 10) cout << 2;
}
if (m == 3)
{
if (n == 1) cout << 1;
if (n == 2) cout << 2;
if (n == 3) cout << 3;
if (n == 4) cout << 3;
if (n == 5) cout << 3;
if (n == 6) cout << 3;
if (n == 7) cout << 3;
if (n == 8) cout << 3;
if (n == 9) cout << 3;
if (n == 10) cout << 3;
}
if (m == 4)
{
if (n == 1) cout << 1;
if (n == 2) cout << 3;
if (n == 3) cout << 4;
if (n == 4) cout << 5;
if (n == 5) cout << 5;
if (n == 6) cout << 5;
if (n == 7) cout << 5;
if (n == 8) cout << 5;
if (n == 9) cout << 5;
if (n == 10) cout << 5;
}
if (m == 5)
{
if (n == 1) cout << 1;
if (n == 2) cout << 3;
if (n == 3) cout << 5;
if (n == 4) cout << 6;
if (n == 5) cout << 7;
if (n == 6) cout << 7;
if (n == 7) cout << 7;
if (n == 8) cout << 7;
if (n == 9) cout << 7;
if (n == 10) cout << 7;
}
if (m == 6)
{
if (n == 1) cout << 1;
if (n == 2) cout << 4;
if (n == 3) cout << 7;
if (n == 4) cout << 9;
if (n == 5) cout << 10;
if (n == 6) cout << 11;
if (n == 7) cout << 11;
if (n == 8) cout << 11;
if (n == 9) cout << 11;
if (n == 10) cout << 11;
}
if (m == 7)
{
if (n == 1) cout << 1;
if (n == 2) cout << 4;
if (n == 3) cout << 8;
if (n == 4) cout << 11;
if (n == 5) cout << 13;
if (n == 6) cout << 14;
if (n == 7) cout << 15;
if (n == 8) cout << 15;
if (n == 9) cout << 15;
if (n == 10) cout << 15;
}
if (m == 8)
{
if (n == 1) cout << 1;
if (n == 2) cout << 5;
if (n == 3) cout << 10;
if (n == 4) cout << 15;
if (n == 5) cout << 18;
if (n == 6) cout << 20;
if (n == 7) cout << 21;
if (n == 8) cout << 22;
if (n == 9) cout << 22;
if (n == 10) cout << 22;
}
if (m == 9)
{
if (n == 1) cout << 1;
if (n == 2) cout << 5;
if (n == 3) cout << 12;
if (n == 4) cout << 18;
if (n == 5) cout << 23;
if (n == 6) cout << 26;
if (n == 7) cout << 28;
if (n == 8) cout << 29;
if (n == 9) cout << 30;
if (n == 10) cout << 30;
}
if (m == 10)
{
if (n == 1) cout << 1;
if (n == 2) cout << 6;
if (n == 3) cout << 14;
if (n == 4) cout << 23;
if (n == 5) cout << 30;
if (n == 6) cout << 35;
if (n == 7) cout << 38;
if (n == 8) cout << 40;
if (n == 9) cout << 41;
if (n == 10) cout << 42;
}
cout << '\n';
}
signed main()
{
int TTT;
cin >> TTT;
while (TTT--) solve();
return 0;
}
同学评论:
帅爆了!
二维数组打表:
cpp
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int a[20][20] = {{1,1,1,1,1,1,1,1,1,1},
{1,2,2,2,2,2,2,2,2,2},
{1,2,3,3,3,3,3,3,3,3},
{1,3,4,5,5,5,5,5,5,5},
{1,3,5,6,7,7,7,7,7,7},
{1,4,7,9,10,11,11,11,11,11},
{1,4,8,11,13,14,15,15,15,15},
{1,5,10,15,18,20,21,22,22,22},
{1,5,12,18,23,26,28,29,30,30},
{1,6,14,23,30,35,38,40,41,42}};
void solve()
{
int m,n;
cin >> m >> n;
cout << a[m-1][n-1] << '\n';
}
signed main()
{
int TTT;
cin >> TTT;
while (TTT--) solve();
return 0;
}
T3 名牌
题目背景
LSU 的学校要举行撕名牌大赛,每个同学在比赛前可以自己选择一个字符串作为自己的名牌。
题目描述
LSU 想根据自己好朋友 CBJ 的名牌 A A A,来决定自己的名牌 B B B。
规则如下:
-
LSU 将 CBJ 的名牌 A A A 复制一份放在末尾,得到字符串 A + A A+A A+A;
-
在上一步得到的字符串中,LSU 任选一个位置,插入一个任意字符,得到自己的名牌 B B B。
现在给定 LSU 的名牌 B B B,你能找出 CBJ 的名牌 A A A 吗?
输入格式
第一行一个整数 N N N 代表 LSU 的名牌 B B B 的长度。
第二行一个长度为 N N N 的字符串 B B B。
输出格式
- 如果能从 B B B 得到多种可行的 A A A ,输出
NOT UNIQUE
。 - 如果能从 B B B 得到唯一可行的 A A A ,输出该唯一解字符串。
- 如果不能从 B B B 得到至少一种可行的 A A A ,输出
NOT POSSIBLE
。
样例 #1
样例输入 #1
9
ABABABABA
样例输出 #1
NOT UNIQUE
样例 #2
样例输入 #2
7
CBXACBA
样例输出 #2
CBA
样例 #3
样例输入 #3
3
XYZ
样例输出 #3
NOT POSSIBLE
提示
数据规模与约定
本题采用捆绑测试。
- 子任务 1( 35 35 35 分): N ≤ 2001 N \le 2001 N≤2001。
- 子任务 2( 65 65 65 分): 2 ≤ N ≤ 2 × 1 0 6 + 1 2 \le N \le 2 \times 10^6+1 2≤N≤2×106+1。
数据保证 B B B 中只包含大写字母。
坑点 & 部分分 & 正解
坑点:字符串长度可能为偶数!
部分分就是暴力做法:
- 枚举任意字符串的下标 i i i
- B B B 字符串去掉这个任意字符
- 拆成左右两部分
- 判断左右是否相等
正解:哈希+前缀和。
- 将时间复杂度从 O ( N 2 ) → O ( N ) O(N^2) \to O(N) O(N2)→O(N)。
- 特判字符串 S S S 长为偶数,不可能
- 计算哈希前缀和数组 h h h, h i h_i hi 表示 s 1 , s 2 , s 3 ... s i s_1,s_2,s_3\dots s_i s1,s2,s3...si 的哈希值
- 预处理出哈希乘数 b a s e base base 的 i i i 次方, p r e i = p r e i − 1 × b a s e pre_i=pre_{i-1}\times base prei=prei−1×base
- 枚举插入字符的位置,比较剩下的字符串中,左右两部分的哈希值是否相等。 [ l , r ] [l,r] [l,r] 的哈希值是 h r − h l − 1 × p r e r − l + 1 h_r-h_{l-1}\times pre_{r-l+1} hr−hl−1×prer−l+1。
- 若左右两部分哈希值相等,
sum++
记录答案。 - 若 s u m ≥ 2 sum\ge2 sum≥2,输出
NOT UNIQUE
- 若 s u m = 1 sum=1 sum=1,输出唯一解
- 若 s u m = 0 sum=0 sum=0,输出
NOT POSSIBLE
正解:
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,mid;
char s[2000010];
int base = 22783;
int la,lb;
int pre[2000010],h[2000010];
string a,b,c,d;
int _hash(int l,int r) {return h[r]-h[l-1]*pre[r-l+1];}
int __hash(int l,int r,int k) {return _hash(l,k-1)*pre[r-k]+_hash(k+1,r);}
int sum;
void solve()
{
cin >> n >> s+1;
mid = (n+1) / 2;
if (n % 2 == 0)
{
cout << "NOT POSSIBLE";
return ;
}
pre[0] = 1;
for (int i = 1;i <= n;i++)
{
pre[i] = pre[i-1] * base;
h[i] = h[i-1] * base + (s[i] - 'A' + 1);
}
la = _hash(mid+1,n);
for (int i = mid+1;i <= n;i++) a.push_back(s[i]);
for (int i = 1;i <= mid;i++)
{
lb = __hash(1,mid,i);
if (la == lb)
{
sum++;
c = a;
break;
}
}
lb = _hash(1,mid-1);
for (int i = 1;i < mid;i++) b.push_back(s[i]);
for (int i = mid;i <= n;i++)
{
la = __hash(mid,n,i);
if (la == lb)
{
sum++;
d = b;
break;
}
}
if (!sum) cout << "NOT POSSIBLE";
else if (sum == 1 || c == d) cout << (c.size() ? c : d);
else cout << "NOT UNIQUE";
}
signed main()
{
int TTT = 1;
// cin >> TTT;
while (TTT--) solve();
return 0;
}
T4 奇怪的商店
题目描述
有一家奇怪的商店,店里的商品不是想买就能买,而是需要先积攒足够的积分和威望。
商店里有 N N N 件商品。想购买第 i i i 件商品,需要买家有至少 a i a_i ai 的积分和 b i b_i bi 的威望,且购买后可以为买家提升 c i c_i ci 的积分和 d i d_i di 的威望。
LSU 很喜欢这家商店,并且想购买店里所有的商品。LSU 可以以任意的顺序进行购买。
现在,LSU 希望你帮忙计算出,要购买所有商品,LSU 的初始积分最小值是多少?在初始积分为这个最小值的前提下,LSU 的初始威望最小值应该是多少?
输入格式
第一行一个整数 T T T,表示该测试点所在的子任务编号。
第二行一个整数 N N N,表示商店内的商品总数。
接下来 N N N 行,每行四个整数,依次表示第 i i i 件商品的 a i , b i , c i , d i a_i,b_i,c_i,d_i ai,bi,ci,di。
输出格式
输出一行两个整数,用空格隔开。分别表示初始积分最小值,及在初始积分最小前提下的初始威望最小值。
样例 #1
样例输入 #1
0
2
1 5 0 2
1 2 3 4
样例输出 #1
1 2
样例 #2
样例输入 #2
3
6
33 33 97 64
27 10 18 2
10 51 43 79
36 98 61 93
56 3 50 77
63 77 19 45
样例输出 #2
10 51
样例 #3
样例输入 #3
7
6
24457404 0 786147346 0
35269648 0 385597498 0
986729974 0 674254286 0
967571555 0 945921862 0
112950075 0 738686121 0
101290103 0 425579434 0
样例输出 #3
24457404 0
提示
样例 1 解释:
当童童的初始积分为 1 1 1,威望为 2 2 2 时,刚好可购买第 2 2 2 件商品。
购买后,童童的积分为 4 4 4,威望为 6 6 6,可购买第 1 1 1 件商品,从而完成购买商店内所有商品。
【数据范围说明】:
本题采用多测试点捆绑测试。
- 子任务 1 1 1( 5 5 5 分):保证 N = 0 N=0 N=0。
- 子任务 2 2 2( 5 5 5 分):保证 N = 1 N=1 N=1。
- 子任务 3 3 3( 20 20 20 分):保证 a i , b i ≤ 100 a_i,b_i \le 100 ai,bi≤100, N ≤ 6 N \le 6 N≤6。
- 子任务 4 4 4( 10 10 10 分):保证 a i , b i ≤ 1 0 5 a_i,b_i \le 10^5 ai,bi≤105, N ≤ 6 N \le 6 N≤6。
- 子任务 5 5 5( 10 10 10 分):保证 a i , b i ≤ 10 a_i,b_i \le 10 ai,bi≤10。
- 子任务 6 6 6( 10 10 10 分):保证 a i , b i ≤ 100 a_i,b_i \le 100 ai,bi≤100。
- 子任务 7 7 7( 10 10 10 分):保证 b i = 0 b_i=0 bi=0, N ≤ 6 N \le 6 N≤6。
- 子任务 8 8 8( 10 10 10 分):保证 b i = 0 b_i=0 bi=0。
- 子任务 9 9 9( 10 10 10 分):保证 N ≤ 6 N \le 6 N≤6。
- 子任务 10 10 10( 10 10 10 分):无特殊约定。
对于全部的测试点,保证 0 ≤ N ≤ 1 0 5 0 \le N \le 10^5 0≤N≤105, 0 ≤ a i , b i , c i , d i ≤ 1 0 9 0 \le a_i,b_i,c_i,d_i \le 10^9 0≤ai,bi,ci,di≤109。
正解
- 先求初始积分的最小值:
将问题转化为有 n n n 个商品,购买需要 a i a_i ai 积分,买完后增加 c i c_i ci 积分,求初始积分的最小值。
简单贪心:按照 a i a_i ai 进行从小到大排序,每次尝试购买积分需求最少的商品,买不了就增加初始积分。 - 再按照威望的要求为第一关键字从小到大,威望的加成为第二关键字从大到小排序。
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int T;
int n,ansa,ansb;
struct node //按照威望
{
int a,b,c,d;
bool operator < (const node &x) const
{
return b != x.b ? b > x.b : d != x.d ? d < x.d : a != x.a ? a > x.a : c < x.c;
}
}a[100010];
priority_queue <node> q;
bool cmp(node a,node b) //按照积分
{
return a.a != b.a ? a.a < b.a : a.c != b.c ? a.c > b.c : a.b != b.b ? a.b < b.b : a.d > b.d;
}
void solve()
{
cin >> T >> n;
for (int i = 1;i <= n;i++) cin >> a[i].a >> a[i].b >> a[i].c >> a[i].d;
sort(a+1,a+n+1,cmp);
for (int i = 1,aa = 0;i <= n;i++) //积分先贪心
{
if (aa < a[i].a) ansa += a[i].a - aa,aa = a[i].a;
aa += a[i].c;
}
cout << ansa << ' '; //先输出积分最小值
for (int i = 1,b = 0;i <= n;i++) //再算威望
{
while (a[i].a > ansa)
{
if (b < q.top().b) ansb += q.top().b - b,b = q.top().b;
b += q.top().d;
ansa += q.top().c;
q.pop();
}
if (a[i].a <= ansa) q.push(a[i]);
if (i == n)
while (!q.empty())
{
if (b < q.top().b) ansb += q.top().b - b,b = q.top().b;
b += q.top().d;
q.pop();
}
}
cout << ansb; //最后输出威望最小值
}
signed main()
{
int TTT;
// cin >> TTT;
TTT = 1;
while (TTT--) solve();
return 0;
}