纪中DAY3做题小结

T1:数列变换

Description
小X 看到堆成山的数列作业十分头疼,希望聪明的你来帮帮他。考虑数列A=[A1,A2,…,An],定义变换f(A,k)=[A2,A3,.Ak,A1,Ak+2,Ak+3,A2k,Ak+1,…],也就是把a 分段,每段k 个(最后如果不足k 个,全部分到新的一段里,见样例),然后将每段的第一个移动到该段的最后一个。
现在,小 X想知道 f (f (f (f ([1,2,3,⋯,n],2),3),⋯),n)的结果。

Input
输入一行包含一个整数n 。

Output
输出一行包含n 个整数,表示最终的数列。

Sample Input

4

Sample Output

4 2 3 1

【样例说明】
f ([1,2,3,4],2) = [2,1,4,3]
f ([2,1,4,3],3) = [1,4,2,3](3单独被分在一组,移动到组的最后一位,仍然是3)
f ([1,4,2,3],4) = [4,2,3,1]

Data Constraint
对于60%的数据,1≤ n ≤10^3。
对于100%的数据,1≤ n ≤10^6。

简要思路(但不正确 ) :一开始,我用数组pos[i]来表示数字i的下标,自以为很高明地想到了与别人不一样的方法,不出意外地只过了60%的数据。

//60分代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e6 + 5;
int pos[N] , num[N];
int n;
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 () {
	scanf("%d",&n);
	for ( int i = 1 ; i <= n ; ++i ) {
		pos[i] = i;
	}
	for ( int j = 1 ; j <= n - 2 ; ++j ) {
		for ( int i = 1 ; i <= n ; ++i ) {
			if ( pos[i] % ( j + 1 ) == 1 ) {
				pos[i] = min( pos[i] + j , n );
			} else {
				--pos[i];
			}
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		if ( pos[i]  == 1 ) {
			pos[i] = n;
		} else {
			--pos[i];
		}
		num[pos[i]] = i;
	}
	printf("%d",num[1]);
	for ( int i = 2 ; i <= n ; ++i ) {
		printf(" %d",num[i]);
	}
	return 0;
}

稍复杂的思路(但正确 ):这是我向机房大佬学来的:注意到总共需要操作的段数是o(nlogn)级别的,所以我们只要想办法把一次移动做到 o(1) 即可。
观察这个所谓的变换,可以发现,相当于:
第一段的开头移到了原来第二段的开头的位置;
第二段的开头移到了原来第三段的开头的位置;
第三段的开头移到了原来第四段的开头的位置;
……
最后一段的开头移到了整个数列的最后。
所以如果我们倒序枚举每一段来把这一段的开头移动到后一段的开头,并用双倍数组维护整个数列(因为整个数列开头和结尾的位置每次会后移),这题就可以通过了。

//满分代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e6 + 5;
int num[N << 1];
int n;
int main () {
	scanf("%d",&n);
	for ( int i = 1 ; i <= n ; ++i ) {
		num[i] = i;
	}
	for ( int i = 2 ; i <= n ; ++i ) {
		num[n + i - 1] = num[(n / i) * i + i - 1];
		for ( int j = ( n / i ) ; j ; --j ) {
			num[i * j + i - 1] = num[i * j - 1];
		}
	}
	for ( int i = n ; i < n * 2 ; ++i ) {
		printf("%d ",num[i]);
	}
	return 0;
} 

T2:卡牌游戏

Description
小X 为了展示自己高超的游戏技巧,在某一天兴致勃勃地找小Y 玩起了一种卡牌游戏。每张卡牌有类型(攻击或防御)和力量值两个信息。
小Y 有n 张卡牌,小X 有m 张卡牌。已知小X 的卡牌全是攻击型的。
游戏的每一轮都由小X 进行操作,首先从自己手上选择一张没有使用过的卡牌X。如果小Y 手上没有卡牌,受到的伤害为X 的力量值,否则小X 要从小Y 的手上选择一张卡牌Y。若Y 是攻击型(当X 的力量值不小于Y 的力量值时才可选择),此轮结束后Y 消失,小Y 受到的伤害为X 的力量值与Y 的力量值的差;若Y 是防御型(当X 的力量值大于Y 的力量值时才可选择),此轮结束后Y 消失,小Y 不受到伤害。
小X 可以随时结束自己的操作(卡牌不一定要用完)。希望聪明的你帮助他进行操作,使得小Y 受到的总伤害最大。

Input
输入的第一行包含两个整数n 和m 。
接下来n 行每行包含一个字符串和一个整数,分别表示小Y 的一张卡牌的类型(“ATK”表示攻击型,“DEF”表示防御型)和力量值。
接下来m 行每行包含一个整数,表示小X 的一张卡牌的力量值。

Output
输出一行包含一个整数,表示小Y 受到的最大总伤害。

Sample Input
输入1:

2 3
ATK 2000
DEF 1700
2500
2500
2500

输入2:

3 4
ATK 10
ATK 100
ATK 1000
1
11
101
1001

Sample Output
输出1:

3000

输出2:

992

【样例说明1】
第一轮,小X 选择自己的第一张卡牌和小Y 的第二张卡牌,小Y 的第二张卡牌消失。
第二轮,小X 选择自己的第二张卡牌和小Y 的第一张卡牌,小Y 的第一张卡牌消失,同时受到500 点伤害。
第三轮,小X 选择自己的第三张卡牌,此时小Y 手上已经没有卡牌,受到2500 点伤害。
小X 结束游戏,小Y 共受到3000点伤害。

【样例说明2】
第一轮,小X 选择自己的第三张卡牌和小Y 的第一张卡牌,小Y 的第一张卡牌消失,同时受到91点伤害。
第二轮,小X 选择自己的第四张卡牌和小Y 的第二张卡牌,小Y 的第二张卡牌消失,同时受到901点伤害。
小X 结束游戏,小Y 共受到992点伤害。

Data Constraint
各规模均有一半数据满足小Y 只有攻击型卡牌。
对于30%的数据,1≤ n,m ≤ 6。
对于60%的数据,1≤ n,m ≤10 ^ 3。
对于100%的数据,1≤ n,m ≤10 ^ 5 ,力量值均为不超过10 ^ 6的非负整数。

简要思路:贪心即可,参照田忌赛马的思路,将小Y的攻守牌分开统计,先用小X的牌从大到小排序,依次怼掉小Y从小到大排的攻牌,遇见怼不动的就结束游戏。若还有剩,用剩下的大于小Y的守牌的最小牌去怼对应的牌,再加上剩下的牌即可(先考虑守牌再考虑攻牌也行)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long//切忌忘开long long
using namespace std;
const int N = 1e5 + 5;
int n , m;
char s[4];
int x[N];
int y1[N];
int y2[N];
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;
}
void c() {
	printf("yes\n");
}
int main () {
	//freopen( "card11.in" , "r" , stdin );
	read(n);
	read(m);
	int n1 = 0 , n2 = 0;
	for ( int i = 1 ; i <= n ; ++i ) {
		cin >> s;
		if ( s[0] == 'A' ) {
			read(y1[++n1]);
		} else {
			read(y2[++n2]);
		}
	}
	for ( int i = 1 ; i <= m ; ++i ) {
		read(x[i]);
	}
	sort( y1 + 1 , y1 + 1 + n1 );
	sort( y2 + 1 , y2 + 1 + n2 );
	sort( x + 1 , x + 1 + m , greater<int>() );//从大到小
	int i = 1;
	ll ans = 0;
	for ( int j = 1 ; i <= m && j <= n1 ; ++j ) {
		if ( x[i] >= y1[j] ) {
			ans += (ll)x[i] - (ll)y1[j];
			++i;
		} else {
			printf("%lld",ans);
			return 0;
		}
	}
	if ( i > m ) {//若无剩牌
		printf("%lld",ans);
		return 0;
	}
	ll add = 0;
	int k = m;
	int j;
	for ( j = 1 ; k >= i && j <= n2 ;  ) {
		if ( x[k] > y2[j] ) {
			--k;
			++j;
		} else {
			add += (ll)x[k];//若最后小Y无牌这些没办法怼掉守牌的较小牌有用
			--k;
		}
	}
	if ( j >= n2 ) {
		ans += add;
	}
	//cout << i << " " << k << endl;
	for ( int l = i ; l <= k ; ++l ) {
		ans += x[l];
	}
	printf("%lld",ans);
	return 0;
}

T3:舞台表演

Description
小X 终于找到了自己的舞台,希望进行一次尽兴的表演。
不妨认为舞台是一个n 行m 列的矩阵,矩阵中的某些方格上堆放了一些装饰物,其他的则是空地。小X 可以在空地上滑动,但不能撞上装饰物或滑出舞台,否则表演就失败了。
小Y 为了让小X 表演得尽量顺畅,提前为小X 写好了每一段时间的移动方向。每个时刻,听话的小X 都会依据小Y 写好的所在时间段的方向(东、西、南、北)向相邻的方格滑动一格。由于小Y 之前没有探查过舞台的情况,如果小X 直接按照小Y 写好的来移动,很容易表演失败。
不过,小Y 是个天使,拥有让小X 停在原地的魔法,也就是某一时刻,小X 以为自己移动了实际上没有移动。为了让小X 表演得尽量完美,小Y 想使小X 在舞台上滑行的路程尽量长(当然不能中途表演失败)。可惜小Y 的智商不足以完成这么复杂的计算,希望你来帮助她决定哪些时刻该使用魔法。当然,她关心的首先是最长的路程是多少。

Input
输入的第一行包含五个整数n,m, x, y 和k 。(x, y )为小 X的初始位置,k 为时间的段数。
接下来n 行每行包含m 个字符,描述这个舞台(“.”表示该位置是空地,“x”表示该位置有装饰物)。
接下来k 行每行包含三个整数si ti di (1<= i<= k ),表示在时间段[si,ti ] 内,小 X的移动方向是di 。di 为1,2,3,4中的一个,依次表示北、南、西、东(分别对应矩阵中的上、下、左、右)

Output
输出一行包含一个整数,表示小X 滑行的最长路程。

Sample Input

4 5 4 1 3
..xx.
.....
...x.
.....
1 3 4
4 5 1
6 7 3

Sample Output

6

Data Constraint
保证输入的时间段是连续的,即 s1 =1 ,si=ti-1+1(1<i<=k) ,tk=t。
对于30%的数据,1≤ t ≤ 20。
对于60%的数据,1≤t ≤ 200。
对于100%的数据,1≤ n,m,k ≤ 200,1≤t ≤10^5。

简要思路:其实这题是瑰丽华尔兹 ,dp + 单调队列优化, d [ k ] [ i ] [ j ] d[k][i][j] d[k][i][j]表示在时间段k走到方格(i,j)的最长路程,使用单调队列优化可以使时间复杂度从o(n ^ 5)降至o(n ^ 3)。具体见代码吧。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 255;
int n , m , sx , sy , k , ans , t , l , r;
struct node{
	int val;
	int sit;
}q[N * N];
int xx[5] = { 0 , -1 , 1 , 0 , 0 };
int yy[5] = { 0 , 0 , 0 , -1 , 1 };
int dp[N][N][N];
int map[N][N];
char ss[205];
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;
}
void doit( int x , int y , int tim ) {
	int head = 1 , tail = 0;
	for ( int i = 1 ; x >= 1 && x <= n && y >= 1 && y <= m ; i++ , x += xx[tim] , y += yy[tim] ) {
		if ( map[x][y] == 1 ) {//发现障碍物
			head = 1;
			tail = 0;
			continue;
		}
		while ( head <= tail && dp[x][y][t - 1] >= q[tail].val + i - q[tail].sit ) {
			tail--;//在该节点后面发现更优节点,而该节点一定比后面的更优节点早出队,后面一直用不上了,故直接踢出
		}
		q[++tail].sit = i;
		q[tail].val = dp[x][y][t - 1];
		while ( head <= tail && i - q[head].sit > r - l + 1  ) {
			head++;//距离已超过当前最长位移
		}
		dp[x][y][t] = q[head].val + i - q[head].sit;
		ans = max( ans , dp[x][y][t] );
	}
	return;
}
int main () {
	//freopen( "testdata.in" , "r" , stdin );
	read(n);
	read(m);
	read(sx);
	read(sy);
	read(k);
	for ( int i = 1 ; i <= n ; ++i ) {
		cin >> ss;
		for ( int j = 1 ; j <= m ; ++j ) {
			if ( ss[j - 1] == 'x' ) {
				map[i][j] = 1;
			} else {
				map[i][j] = 0;
			}
		}
	}
	memset( dp , 0xcf , sizeof(dp) );//0xcf赋极小值
	dp[sx][sy][0] = 0;
	int tt;
	for ( int i = 1 ; i <= k ; ++i ) {
		t = i;
		read(l);
		read(r);
		read(tt);
		switch(tt) {//在一定条件下,switch比if快
			case 1 :{//从边界dp可充分考虑所有情况
				for ( int j = 1 ; j <= m ; ++j ) {
					doit( n , j , tt );
				}
				break;
			}
			case 2 :{
				for ( int j = 1 ; j <= m ; ++j ) {
					doit( 1 , j , tt );
				}
				break;
			}
			case 3 :{
				for ( int j = 1 ; j <= n ; ++j ) {
					doit( j , m , tt );
				}
				break;
			}
			case 4 :{
				for ( int j = 1 ; j <= n ; ++j ) {
					doit( j , 1 , tt );
				}
				break;
			}
		}
	}
	printf("%d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值