纪中DAY9做题小结
T1:rank
Description
小h和小R正在看之前的期末&三校联考成绩,小R看完成绩之后很伤心,共有n(n<=5*10 ^ 6)个学生,第i个学生有一个总成绩Xi(0<=Xi<=10 ^ 5),因为他的排名是倒数第k(1<=k<=n)个,于是小R想知道那些成绩比他低(包括成绩和他一样)的同学的成绩,这样能让他没那么伤心。
Input
第一行,n和k,表示有n个学生,小R排倒数第k.
第二行,n个非负整数,表示这n个学生的成绩。
Output
一行,共k个数,从小到大输出。(相同成绩按不同排名算)
Sample Input
5 3
1 1 2 2 3
Sample Output
1 1 2
简要思路:本题只是一道非常简单的排序题,而且时限有2000ms,(大概是因为昨天的爆零现场太惨烈了,出题人也怕了 )。有人担心会出现极端数据,用了桶排,本人表示没必要啊。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e6 + 5;
int n , k;
int num[N];
inline 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);
read(k);
for ( int i = 1 ; i <= n ; ++i ) {
read(num[i]);
}
sort( num + 1 , num + 1 + n );
printf("%d",num[1]);
for ( int i = 2 ; i <= k ; ++i ) {
printf(" %d",num[i]);
}
return 0;
}
T2:seek
Description
俗话说“好命不如好名”,小h准备给他的宠物狗起个新的名字,于是他把一些英文的名字全抄下来了,写成一行长长的字符串,小h觉得一个名字如果是好名字,那么这个名字在这个串中既是前缀,又是后缀,即是这个名字从前面开始可以匹配,从后面开始也可以匹配,例如abc在 abcddabc中既是前缀,也是后缀,而ab就不是,可是长达4*10^5的字符让小h几乎昏过去了,为了给自己的小狗起个好名字,小h向你求救,并且他要求要将所有的好名字的长度都输出来。
Input
一行,要处理的字符串(都是小写字母)。
Output
一行若干个数字,从小到大输出,表示好名字的长度。
Sample Input
abcddabc
Sample Output
3 8
简要思路:本题是一道稍微拓展了一下的KMP(哈希也可以,但我不会 )。在KMP中,数组
p
[
i
]
p[i]
p[i]表示字符串中若第
i
i
i个字符失配,则会向前至少跳到的位置上,根据KMP的原理,若
p
[
i
]
p[i]
p[i]的值为
j
j
j,证明从
i
i
i开始向前
j
j
j个字符形成的字符串与整个原字符串的前
j
j
j个完全相同(我知道配图更好,可我不会画 )。设字符串为
s
s
ss
ss长度为
n
n
n,因为
s
s
[
0...
p
[
n
]
−
1
]
ss[0 ...p[n] - 1]
ss[0...p[n]−1]与
s
s
[
n
−
1
−
p
[
n
]
.
.
.
n
−
1
]
ss[n - 1 - p[n]...n - 1]
ss[n−1−p[n]...n−1]完全相同,若存在一个字符串
s
r
sr
sr为
s
s
[
0...
p
[
n
]
−
1
]
ss[0 ...p[n] - 1]
ss[0...p[n]−1]的前后缀,则
s
r
sr
sr也必为整个字符串的前后缀,故
n
.
.
.
p
[
n
]
.
.
p
[
p
[
n
]
]
.
.
.
p
[
p
[
p
[
n
]
]
]
n... p[n]..p[p[n]]...p[p[p[n]]]
n...p[n]..p[p[n]]...p[p[p[n]]]一直到零(不含)均为答案,详见代码。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 4e5 + 5;
char ss[N];
int p[N] , ans[N];
int tot;
int main () {
scanf("%s",(ss + 1));
int len = strlen( ss + 1 );
int j = 0;
p[1] = 0;
for ( int i = 1 ; i <= len - 1 ; ++i ) {
while ( j && ss[i + 1] != ss[j + 1] ) {
j = p[j];
}
if ( ss[i + 1] == ss[j + 1] ) {
j++;
}
p[i + 1] = j;
}
tot = 0;
int n = len;
while ( n ) {
ans[++tot] = n;
n = p[n];
}
printf("%d",ans[tot]);
tot--;
for ( int i = tot ; i >= 1 ; --i ) {
printf(" %d",ans[i]);
}
return 0;
}
T3:pot
Description
这个假期,小h在自家院子里种了许多花,它们围成了一个圈,从1…n编号(n<=100000),小h 对每盆花都有一个喜好值xi,(-1000<=xi<=1000),小h现在觉得这样一成不变很枯燥,于是他做了m(m<=100000)个改动,每次把第ki盘花改成喜好值为di的花,然后小h要你告诉他,在这个花圈中,连续的最大喜好值是多少。
Input
第一行,n,花盆的数量
第二行,n个数,表示对于每盆花的喜好值。
第三行:m, 改动的次数
以下m行,每行两个数ki 和di 。
Output
M行,每一行对应一个更改,表示连续的最大喜好值,且不能整圈都选。(注意:是在圈上找)
Sample Input
5
3 -2 1 2 -5
4
2 -2
5 -5
2 -4
5 -1
Sample Output
4
4
3
5
简要思路:本题是要在一个环上维护最大区间和,但区间不能包括整个环。考虑到一个环可能会从最大和区间断开,我们还可以维护一个最小区间和(整个数列由一个最大和区间跟一个最小和区间构成,可用反证法证明,并且环的断点只可能断开两个区间中的一个),用环的总值减去最小区间和,得到另一个预选答案,将两者比较输出较大值即可。
区间维护要利用区间合并,具体方法见代码吧。
本题还要特判,当环上所有数为正数时,我们维护的最大区间和恰好等于环的总值,此时我们维护的最小区间和也正好为环上最小的一个数,此时答案只能用环的总值减去最小区间和得出。
#include <iostream>
#include <cstdio>
#include <cstring>
#define ls pos << 1
#define rs pos << 1 | 1
using namespace std;
const int N = 1e5 + 5 , INF = 0x7fffffff;
int n , m;
inline 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;
}
inline int max( int a , int b ) {
return a > b ? a : b;
}
inline int min( int a , int b ) {
return a < b ? a : b;
}
struct segmenttree{
int sum[N << 2] , lmax[N << 2] , rmax[N << 2] , lmin[N << 2] , rmin[N << 2] , midmax[N << 2] , midmin[N << 2];//意义根据单词就可以猜出来了
void upt( int pos ) {//lmax表示包括最左端的最大区间和, rmax表示包括最右端的最大区间和,midmax表示数列范围内最大区间和,min同理
sum[pos] = sum[ls] + sum[rs];
lmax[pos] = max( lmax[ls] , sum[ls] + lmax[rs] );
rmax[pos] = max( rmax[rs] , sum[rs] + rmax[ls] );
lmin[pos] = min( lmin[ls] , sum[ls] + lmin[rs] );
rmin[pos] = min( rmin[rs] , sum[rs] + rmin[ls] );
midmax[pos] = max( lmax[rs] + rmax[ls] , max( midmax[ls] , midmax[rs] ) );//合并右儿子的lmax与左儿子的rmax可得到父亲节点的midmax
midmin[pos] = min( lmin[rs] + rmin[ls] , min( midmin[ls] , midmin[rs] ) );
return;
}
void pre( int pos , int l , int r ) {
sum[pos] = lmax[pos] = rmax[pos] = midmax[pos] = -INF;
lmin[pos] = rmin[pos] = midmin[pos] = INF;
if ( l == r ) {
return;
}
int mid = ( l + r ) >> 1;
pre( ls , l , mid );
pre( rs , mid + 1 , r );
return;
}
void change( int pos , int aim , int val , int l , int r ) {
if ( l == r ) {
sum[pos] = lmin[pos] = lmax[pos] = rmin[pos] = rmax[pos] = midmin[pos] = midmax[pos] = val;
return;
}
int mid = ( l + r ) >> 1;
if ( mid >= aim ) {
change( ls , aim , val , l , mid );
} else {
change( rs , aim , val , mid + 1 , r );
}
upt(pos);
return;
}
}tree;//用结构体封装线段树感觉不错
int main () {
read(n);
tree.pre( 1 , 1 , n );
int tem;
for ( int i = 1 ; i <= n ; ++i ) {
read(tem);
tree.change( 1 , i , tem , 1 , n );
}
read(m);
int k , d;
for ( int i = 1 ; i <= m ; ++i ) {
read(k);
read(d);
tree.change( 1 , k , d , 1 , n );
if ( tree.sum[1] == tree.midmax[1] ) {//不能包括整个环
printf("%d\n",tree.sum[1] - tree.midmin[1]);
} else {
printf("%d\n",max( tree.sum[1] - tree.midmin[1] , tree.midmax[1] ));
}
}
return 0;
}
本题(T3)我打了一个暴力的树状数组,只拿了二十分。
T4:游戏节目(show)
Description
有三支队伍,分别是A,B,C。有n个游戏节目,玩第i个游戏,队伍A可以得到的分数是A[i],队伍B可以得到的分数是B[i],队伍C可以得到的分数是C[i]。由于时间有限,可能不是每个节目都能玩,于是节目主持人决定要从n个游戏节目里面挑选至少k个节目出来(被选中的节目不分次序),使得队伍A成为赢家。队伍A能成为赢家的条件是队伍A的总得分要比队伍B的总得分要高,同时也要比队伍C的总得分要高。节目主持人有多少种不同的选取方案?
Input
第一行,两个整数n和k。
第二行, n个整数,分别是A[1]、A[2]、A[3]…A[n]。
第三行, n个整数,分别是B[1]、B[2]、B[3]…B[n]。
第四行, n个整数,分别是C[1]、C[2]、C[3]…C[n]。
Output
一个整数,表示不同的选取方案数量。
Sample Input
3 2
1 1 2
1 1 1
1 1 1
Sample Output
3
【样例解释】
方案一:选取节目1和节目3。
方案二:选取节目2和节目3。
方案三:选取节目1、节目2、节目3。
Data Constraint
对于40%数据,2 <= n <= 20。
对于100%数据,2 <= n <= 34, 1 <= k <= min(n,7), 1 <=A[i], B[i], C[i]<= 10^9。
简要思路:本题我没什么思路,一开始打了一个用状压统计节目选择状态的暴力,果不其然只有四十分暴力分。
//四十分代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#define ll long long
using namespace std;
int n , k , tot;
int numa[36] , numb[36];
ll ans;
map< ll , int > aa , bb;
inline 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 () {
//freopen( "show.in" , "r" , stdin );
//freopen( "show.out" , "w" , stdout );
read(n);
read(k);
ans = 0;
for ( int i = 1 ; i <= n ; ++i ) {
read(numa[i]);
}
for ( int i = 1 ; i <= n ; ++i ) {
read(numb[i]);
}
int tem , c;
for ( int i = 1 ; i <= n ; ++i ) {
read(c);
tem = numa[i];
numa[i] = numa[i] - numb[i];
numb[i] = tem - c;
}
aa[0] = 0;
bb[0] = 0;
ll end = (ll)( 1ll << (ll)n ) - 1ll;
for ( ll i = 0 ; i <= end - 1 ; ++i ) {
tot = 0;
for ( ll j = 0 ; j <= (ll)n - 1 ; ++j ) {
if ( i & ( 1ll << j ) ) {
++tot;
} else {
aa[i|( 1ll << (ll)j )] = aa[i] + numa[j + 1];
bb[i|( 1ll << (ll)j )] = bb[i] + numb[j + 1];
}
}
if ( tot >= k && aa[i] > 0 && bb[i] > 0 ) {
ans++;
}
}
if ( aa[end] > 0 && bb[end] > 0 ) {
ans++;
}
printf("%lld",ans);
return 0;
}
后面经过一些dalao的讲解,我才get本题的正解思路,话说这很像cdq分治,如果我猜错了请dalao轻喷 。
首先,我们可以暴力穷举
i
(
节
目
数
)
<
k
i(节目数) \lt k
i(节目数)<k的情况,毕竟
k
k
k最大只有7,那么由排列组合公式
C
34
0
+
C
34
1
+
C
34
2
+
C
34
3
+
C
34
4
+
C
34
5
+
C
34
6
=
1676116
C^0_{34} + C^1_{34} + C^2_{34} + C^3_{34} + C^4_{34} + C^5_{34} + C^6_{34} = 1676116
C340+C341+C342+C343+C344+C345+C346=1676116,穷举最多1676116次,那就暴力计算吧,将答案记为
a
n
s
1
ans1
ans1,将所有的(没有节目数限制)的安排方法数记为
a
n
s
2
ans2
ans2,最后算出
a
n
s
2
−
a
n
s
1
ans2 - ans1
ans2−ans1即为最终答案。
不过,我们发现
n
n
n最大为34,穷举复杂度很高,将34分治成两个17后的复杂度就可以接受。考虑将数据分为两组,设为A组,B组,记录穷举后所有的状态数。若A组中的元素能与B组中的结合,设
A
.
X
=
A
.
a
−
A
.
b
A.X = A.a - A.b
A.X=A.a−A.b
A
.
Y
=
A
.
a
−
A
.
c
A.Y = A.a - A.c
A.Y=A.a−A.c,B同理,则
A
.
X
+
B
.
X
>
0
且
A
.
Y
+
B
.
Y
>
0
A.X + B.X \gt 0 且A.Y + B.Y \gt 0
A.X+B.X>0且A.Y+B.Y>0转化为
A
.
X
>
−
B
.
X
且
A
.
Y
>
−
B
.
Y
A.X \gt -B.X且 A.Y \gt -B.Y
A.X>−B.X且A.Y>−B.Y,所以,一组不变,一组取反,更有利于统计合并形成的方法数。
只要一个关键字排序,另一个先离散化后用树状数组或线段树统计组合方法数即可。
那么,A,B组内的组合怎么统计呢?
答案是,A,B组内各预处理一个关键字X,Y全为零的元素,分别用于统计对方组内部的方法数。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
ll n , k , ans1 , ans2;
ll numa[36] , numb[36] , numc[36];
struct no{
ll x;//(a - b)
ll y;//(a - c)
}t1[1500005],t2[1500005];
ll num1 , num2;
int pval , last , now;
struct node {
ll x;
ll y;
ll z;//(标记所在区间)
}d[3000005];
ll tree[1500005];
inline void read( ll & res ) {
res = 0;
ll 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;
}
inline void dfs1( int maxdepth , int depth , int l , ll a , ll b , ll c ) {
if ( depth > maxdepth ) {
if ( a - b > 0 && a - c > 0 ) {
ans1++;
}
return;
}
for ( int i = l + 1 ; i <= n ; ++i ) {
dfs1( maxdepth , depth + 1 , i , a + numa[i] , b + numb[i] , c + numc[i] );
}
return;
}
inline void dfs2( int l , ll a , ll b , ll c ) {
t1[++num1].x = a - b;
t1[num1].y = a - c;
for ( int i = l + 1 ; i <= n / 2 ; ++i ) {
dfs2( i , a + numa[i] , b + numb[i] , c + numc[i] );
}
return;
}
inline void dfs3( int l , ll a , ll b , ll c ) {
t2[++num2].x = b - a;//取反,原因已解释过了
t2[num2].y = c - a;
for ( int i = l + 1 ; i <= n ; ++i ) {
dfs3( i , a + numa[i] , b + numb[i] , c + numc[i] );
}
return;
}
//t1,t2各有一个x,y全为零的元素,这是为了方便统计t1,t2中的安排方式不与另一组结合的情况
inline bool cmp1( node a , node b ) {
return a.y < b.y;
}
inline bool cmp2( node a , node b ) {
if ( a.x != b.x ) {
return a.x < b.x;
} else {
return a.y < b.y;
}
}
inline int lowbit( int x ) {
return x & (-x);
}
inline void update( int x , int y ) {
for ( ; x <= pval ; x += lowbit(x) ) {
tree[x] += y;
}
return;
}
inline ll query( int x ) {
ll res = 0;
for ( ; x ; x -= lowbit(x) ) {
res += tree[x];
}
return res;
}
int main () {
//freopen( "show.in" , "r" , stdin );
//freopen( "show.out" , "w" , stdout );
ans1 = 0;
ans2 = 0;
read(n);
read(k);
for ( int i = 1 ; i <= n ; ++i ) {
read(numa[i]);
}
for ( int i = 1 ; i <= n ; ++i ) {
read(numb[i]);
}
for ( int i = 1 ; i <= n ; ++i ) {
read(numc[i]);
}
for ( int i = 1 ; i <= k - 1 ; ++i ) {
dfs1( i , 1 , 0 , 0 , 0 , 0 );
}
dfs2( 0 , 0 , 0 , 0 );
dfs3( n / 2 , 0 , 0 , 0 );
for ( int i = 1 ; i <= num1 ; ++i ) {
d[i].x = t1[i].x;
d[i].y = t1[i].y;
d[i].z = 1;
}
for ( int i = num1 + 1 ; i <= num1 + num2 ; ++i ) {
d[i].x = t2[i - num1].x;
d[i].y = t2[i - num1].y;
d[i].z = 2;
}
sort( d + 1 , d + 1 + num1 + num2 , cmp1 );
pval = 0 , last = 1;
d[num1 + num2 + 1].y = -10000;
for ( int i = 2 ; i <= num1 + num2 + 1 ; ++i ) {
if ( d[i].y != d[i - 1].y ) {
pval++;
for ( int j = last ; j <= i - 1 ; ++j ) {
d[j].y = pval;//将y离散化
}
last = i;
}
}
sort( d + 1 , d + 1 + num1 + num2 , cmp2 );
now = 1;
ll tem;
while ( now <= num1 + num2 ) {
last = now;
tem = d[now].x;
while ( now <= num1 + num2 && d[now].x == tem ) {
now++;
}
for ( int i = last ; i <= now - 1 ; ++i ) {
if ( d[i].z == 1 ) {
ans2 += query( d[i].y - 1 );//区间统计
}
}
for ( int i = last ; i <= now - 1 ; ++i ) {
if ( d[i].z == 2 ) {
update( d[i].y , 1 );
}
}
}
printf("%lld",ans2 - ans1);
return 0;
}
本题不一定考了什么高级算法,但这思维方式非常值得学习。