纪中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[n1p[n]...n1]完全相同,若存在一个字符串 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 ( 节 目 数 ) &lt; 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 ans2ans1即为最终答案。
不过,我们发现 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.aA.b A . Y = A . a − A . c A.Y = A.a - A.c A.Y=A.aA.c,B同理,则 A . X + B . X &gt; 0 且 A . Y + B . Y &gt; 0 A.X + B.X \gt 0 且A.Y + B.Y \gt 0 A.X+B.X>0A.Y+B.Y>0转化为 A . X &gt; − B . X 且 A . Y &gt; − B . Y A.X \gt -B.X且 A.Y \gt -B.Y A.X>B.XA.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;
}

本题不一定考了什么高级算法,但这思维方式非常值得学习。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值