纪中DAY13做题小结

T1:旅游(travel)

Description
ztxz16如愿成为码农之后,整天的生活除了写程序还是写程序,十分苦逼。终于有一天,他意识到自己的生活太过平淡,于是决定外出旅游丰富阅历。
ztxz16生活的城市有NM个景点,可以描述成一个NM的矩形,每个景点有一个坐标(x, y) (1 <= x <= N, 1 <= y <= M)以及美观度 A [ x ] [ y ] A[x][y] A[x][y]和观赏所需的时间 B [ x ] [ y ] B[x][y] B[x][y],从一个景点(x1, y1)走到另一个景点(x2, y2)需要时间为它们之间的曼哈顿距离:|x1 - x2| +|y1 - y2|。
为了防止审美疲劳,ztxz16希望观赏的景点的的美观度是严格上升的,由于不想太早回家码代码,ztxz16希望旅游的总时间尽可能长。

Input
第一行输入两个整数N, M;
接下来N行每行M个整数,第x行第y个整数代表 A [ x ] [ y ] A[x][y] A[x][y]
接下来N行每行M个整数,第x行第y个整数代表 B [ x ] [ y ] B[x][y] B[x][y]
注意,有一些 A [ x ] [ y ] = B [ x ] [ y ] = 0 A[x][y]=B[x][y]=0 A[x][y]=B[x][y]=0,说明这个景点已经拆除,不能游览;

Output
输出一行代表最长的总时间。

Sample Input

4 5
1 2 6 0 2
1 3 4 0 4
0 0 4 0 3
2 2 0 0 4
1 3 5 0 2
2 8 1 0 2
0 0 3 0 4
0 5 0 0 3

Sample Output

39

Hint
【样例说明】
游览顺序为(2,1)->(1,5)->(2,2)->(4,5)->(1,3)

Data Constraint
对于30%的数据:1<=N<=50 , 1<=M<=50
对于60%的数据:1<=N<=300 , 1<=M<=300
对于100%的数据:1<=N<=1000 , 1<=M<=1000
0 &lt; = A [ x ] [ y ] &lt; = 1000000 0&lt;=A[x][y]&lt;=1000000 0<=A[x][y]<=1000000
0 &lt; = B [ x ] [ y ] &lt; = 1 0 9 0&lt;=B[x][y]&lt;=10^9 0<=B[x][y]<=109
注意:本题输入数据较大,请注意输入消耗的时间

简要思路:本题是一道贪心加上DP的题,因为要尽可能有更多的游览时间,所以游览时先游览美观度最低的景点,游览下一个时尽量也选美观度低的,也就是比上一个美观度高的美观度最小的景点,不难发现,这样的状态转移可以构成一个DAG拓扑图。然后就是模拟了,这个做法在最糟的情况下复杂度是 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)但是因为数据水所以能过 。据说这题可用四边形不等式优化,不过本蒟蒻不会

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1005;
int n , m , cnt , lx , ly;
ll ans;
int minn , maxn;
ll d[N][N];
int b[N][N];
struct node{
	int val;
	int x;
	int y;
}a[N * N];
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline bool cmp( node x , node y ) {
	return x.val < y.val;
}
inline int dis( int x1 , int y1 , int x2 , int y2 ) {
	return abs( x1 - x2 ) + abs( y1 - y2 );
}
inline void doit( int h1 , int t1 , int h2 , int t2 ) {
	for ( int i = h1 ; i <= t1 ; ++i ) {
		for ( int j = h2 ; j <= t2 ; ++j ) {
			d[a[j].x][a[j].y] = max( d[a[j].x][a[j].y] , d[a[i].x][a[i].y] + b[a[j].x][a[j].y] + (ll)dis( a[i].x  , a[i].y , a[j].x , a[j].y ) );
		}
	}
	return;
}
int main () {
	read(n);
	read(m);
	cnt = 0;
	minn = 0x3f3f3f3f;
	maxn = -1;
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = 1 ; j <= m ; ++j ) {
			read(a[++cnt].val);
			a[cnt].x = i;
			a[cnt].y = j;
			maxn = max( maxn , a[cnt].val );
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = 1 ; j <= m ; ++j ) {
			read(b[i][j]);
			if ( b[i][j] || a[( i - 1 ) * m + j].val ) {
				minn = min( minn , a[( i - 1 ) * m + j].val );
			}
		}
	}
	sort( a + 1 , a + 1 + cnt , cmp );
	int last = minn;
	lx = 0x3f3f3f3f;
	for ( int i = 1 ; i <= cnt ; ++i ) {
		if ( a[i].val == minn ) {
			lx = min( lx , i );
			d[a[i].x][a[i].y] = b[a[i].x][a[i].y];
		} else if ( a[i].val > minn ) {
			last = a[i].val;
			ly = i - 1;
			break;
		}
	}
	for ( int i = 1 ; i <= cnt ; ++i ) {
		if ( a[i].val > last ) {
			doit( lx , ly , ly + 1 , i - 1 );
			lx = ly + 1;
			ly = i - 1;
			last = a[i].val;
		}
	}
	doit( lx , ly , ly + 1 , cnt );
	ans = 0;
	for ( int i = cnt ; i >= 1 ; --i ) {
		if ( a[i].val == maxn ) {
			ans = max( ans , d[a[i].x][a[i].y] );
		} else {
			break;
		}
	}
	printf("%lld",ans);
	return 0;

T2:做梦(dream)

Description
ztxz16旅游归来后十分疲倦,很快就进入了梦中。
在梦中ztxz16结婚生子了,他不得不照顾小宝宝。但这实在太无聊了,于是ztxz16会在散步。梦中ztxz16住在一个类似数轴的街上,数轴上的每个整点是一个街区,每个单位时间内ztxz16可以选择向左走一个街区或者向右走一个街区,但如果他离开家超过m个单位时间小宝宝会有危险,因此ztxz16必须在距离上次在家中不超过m个单位时间内回到家中。
n个单位时间后ztxz16会醒来,他希望此时正好在家中。
ztxz16想知道散步过程可能有多少种不同的散步过程。两个散步过程被认为不同,当且仅当存在至少一个单位时刻ztxz16选择的走向不同。

Input
第一行输入两个整数n, m。

Output
输出可能的散步过程数%1000000007。

Sample Input
输入1:

4 2

输入2:

10 6

Sample Output
输出1:

4

输出2:

184

Data Constraint
对于30%的数据:2<=n<=100, 2<=m<=100
对于100%的数据:2<=n<=10^9, 2<=m<=100
n和m均为偶数

简要思路:这题我能敲出代码,但知其然不知其所以然,只知道要用到卡特兰数,等以后明白得更透彻再讲吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define mod 1000000007;
using namespace std;
ll c[105][105];
int n , m;
struct node{
	ll matrix[105][105];
	friend node operator * ( node a , node b ) {
		node tem;
		memset( tem.matrix , 0 , sizeof(tem.matrix) );
		for ( int i = 1 ; i <= m / 2 ; ++i ) {
			for ( int j = 1 ; j <= m / 2 ; ++j ) {
				for ( int k = 1 ; k <= m / 2 ; ++k ) {
					tem.matrix[i][j] += a.matrix[i][k] * b.matrix[k][j];
					tem.matrix[i][j] %= mod;
				}
			}
		}
		return tem;
	}
}f , ans;
inline void cal( node a , node b ) {
	ans = a * b;
} 
inline void dfs( int num ) {
	if ( !num ) {
		return;
	}
	if ( num % 2 == 1 ){
		dfs( num - 1 );
	} else {
		dfs( num / 2 );
	}
	if ( num % 2 == 1 ) {
		cal( ans , f );
	} else {
		cal( ans , ans );
	}
}
int main () {
	memset( c , 0 , sizeof(c) );
	c[1][1] = 1;
	for ( int i = 1 ; i <= 100 ; ++i ) {
		for ( int j = 1 ; j <= 50 ; ++j ) {
			if ( c[i][j] ) {
				c[i + 1][j + 1] = ( c[i + 1][j + 1] + c[i][j] ) % mod;
				c[i + 1][j - 1] = ( c[i + 1][j - 1] + c[i][j] ) % mod;
			}
		}
	}
	scanf("%d%d",&n,&m);
	for ( int i = 1 ; i <= m / 2 ; ++i ) {
		f.matrix[1][i] = (c[2 * i][0] * 2) % mod;//表示i - 1(0对应1的卡特兰数)对应的卡特兰数
	}
	for ( int i = 2 ; i <= m / 2 ; ++i ) {
		f.matrix[i][i - 1] = 1;
	}
	for ( int i = 1 ; i <= m / 2 ; ++i ) {
		ans.matrix[i][i] = 1;
	}
	dfs(n/2);
	printf("%lld",ans.matrix[1][1]);
	return 0;
}

T3:数数(count)

Description
ztxz16从小立志成为码农,因此一直对数的二进制表示很感兴趣。今天的数学课上,ztxz16学习了等差数列的相关知识。我们知道,一个等差数列可以用三个数A,B,N表示成如下形式:
B + A, B + 2 * A, B + 3 * A, …, B + N * A
ztxz16想知道对于一个给定的等差数列,把其中每一项用二进制表示后,一共有多少位是1,但他的智商太低无法算出此题,因此寻求你的帮助。

Input
第一行输入一个整数T代表数据组数;
接下来T行每行输入三个整数A,B,N;

Output
输出T行,每行一个整数代表答案。

Sample Input

2
4 7 1
5 8 2

Sample Output

3
5

Data Constraint
对于30%的数据:
1 &lt; = T &lt; = 20 , 1 &lt; = A &lt; = 10000 , 1 &lt; = B &lt; = 1 0 16 , 1 &lt; = N &lt; = 1 0 3 1&lt;=T&lt;=20 , 1&lt;=A&lt;=10000 , 1&lt;=B&lt;=10^{16} , 1&lt;=N&lt;=10^3 1<=T<=20,1<=A<=10000,1<=B<=1016,1<=N<=103
对于60%的数据:
1 &lt; = T &lt; = 20 , 1 &lt; = A &lt; = 10000 , 1 &lt; = B &lt; = 1 0 16 , 1 &lt; = N &lt; = 1 0 9 1&lt;=T&lt;=20 , 1&lt;=A&lt;=10000 , 1&lt;=B&lt;=10^{16} , 1&lt;=N&lt;=10^9 1<=T<=20,1<=A<=10000,1<=B<=1016,1<=N<=109
对于100%的数据:
1 &lt; = T &lt; = 20 , 1 &lt; = A &lt; = 10000 , 1 &lt; = B &lt; = 1 0 16 , 1 &lt; = N &lt; = 1 0 12 1&lt;=T&lt;=20 , 1&lt;=A&lt;=10000 , 1&lt;=B&lt;=10^{16} , 1&lt;=N&lt;=10^{12} 1<=T<=20,1<=A<=10000,1<=B<=1016,1<=N<=1012

简要思路:本题涉及类欧几里得(总算是我的了 ^ _ ^),不会的建议自学,否则下面的很难理解。
其实这题可以用数位DP,但是因为细节处理太恶心加上老师说过,大佬如果看见我们这些蒟蒻用数位DP切这道裸的(我觉得不裸 )类欧几里得,是会嘲笑我们的,于是乎,我宁愿自学类欧以解决这题。
这道题用一个式子来概括答案就是(题中数据最大换算成二进制也只有51位) A n s = ∑ k = 0 51 ∑ i = 1 n ⌊ a + b ⋅ i 2 k ⌋ − ⌊ a + b ⋅ i 2 k + 1 ⌋ ∗ 2 Ans = \sum^{51}_{k = 0} \sum^n_{i = 1}\lfloor{ a + b \cdot i \over 2^k } \rfloor - \lfloor{a + b \cdot i \over 2^ {k + 1} } \rfloor * 2 Ans=k=051i=1n2ka+bi2k+1a+bi2
先把从首位到第k位的答案算出,结果是从最后面到第k位的值,第k + 1位同理,算出的从最后面到第k + 1位的值,乘2后可左移一位,第k位的值减去两倍第k + 1位的值,得出的结果若为一,证明本位为一,计入贡献,而用类欧几里得算出的总值就是第k位在i = 1 ~ n的总贡献。
最后再说一点,本题数据过大,但高精又不方便,建议用unsigned long long。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ull unsigned long long
using namespace std;
ull er[66];
inline void read( ull & res ) {
	res = 0;
	ull pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
ull f( ull a , ull b , ull c , ull n ) {
	ull s = ( n + 1 ) * ( b / c );
	if ( !( n & 1 ) ) {//数据过大,n*(n + 1)不能出现,又为了防止整除破坏数据,特判奇偶性 
		s += n / 2 * ( n + 1 ) * ( a / c );
	} else {
		s += (n + 1) / 2 * n * (a / c);
	}
	a %= c;
	b %= c;
	ull m = ( a * n + b ) / c;
	if ( !m ) {//这里包括!a,因为 a == 0时m必为0 
		return s;
	}
	return s + n * m - f( c , c - b - 1 , a , m - 1 );
}//这是经过一些变动的类欧几里得(数据过大,传统写法会炸) 
int main () {
	ull a , b , n;
	int t;
	er[0] = 1;
	for ( int i = 1 ; i <= 62 ; ++i ) {
		er[i] = er[i - 1] * 2;
	}
	scanf("%d",&t);
	while ( t-- ) {
		read(a);
		read(b);
		read(n);
		ull ans = 0;
		for ( int i = 0 ; i <= 60 ; ++i ) {
			if ( b & er[i] ) {
				ans--;//类欧几里得包括i为零的情况,题中i至少为1,这里手动去除
			}
		}
		for ( int i = 0 ; i <= 60 ; ++i ) {
			ans += f( a , b , er[i] , n ) - f( a , b , er[i + 1] , n ) * 2;//一位一位地算贡献
		}
		printf("%llu\n",ans);
	}
	return 0;
}

可能有人发现这和传统写法不一样。

	ull s = ( n + 1 ) * ( b / c );
	if ( !( n & 1 ) ) {
		s += n / 2 * ( n + 1 ) * ( a / c );
	} else {
		s += (n + 1) / 2 * n * (a / c);
	}
	a %= c;
	b %= c;
	ull m = ( a * n + b ) / c;
	if ( !m ) {
		return s;
	}
	return s + n * m - f( c , c - b - 1 , a , m - 1 );
	if ( !a ) {
		return ( n + 1 ) * ( b / c );
	}
	if ( a > c || b > c ) {
		return n * ( n + 1 ) / 2 * ( a / c ) + ( n + 1 ) * ( b / c ) + f( a % c , b % c , c , n );
	}
	ull m = ( a * n + b ) / c;
	return n * m - f( c , c - b - 1 , a , m - 1 );

仔细对比,不难发现两者意义是相同的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值