纪中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 < = A [ x ] [ y ] < = 1000000 0<=A[x][y]<=1000000 0<=A[x][y]<=1000000
0 < = B [ x ] [ y ] < = 1 0 9 0<=B[x][y]<=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 < = T < = 20 , 1 < = A < = 10000 , 1 < = B < = 1 0 16 , 1 < = N < = 1 0 3 1<=T<=20 , 1<=A<=10000 , 1<=B<=10^{16} , 1<=N<=10^3 1<=T<=20,1<=A<=10000,1<=B<=1016,1<=N<=103
对于60%的数据:
1 < = T < = 20 , 1 < = A < = 10000 , 1 < = B < = 1 0 16 , 1 < = N < = 1 0 9 1<=T<=20 , 1<=A<=10000 , 1<=B<=10^{16} , 1<=N<=10^9 1<=T<=20,1<=A<=10000,1<=B<=1016,1<=N<=109
对于100%的数据:
1 < = T < = 20 , 1 < = A < = 10000 , 1 < = B < = 1 0 16 , 1 < = N < = 1 0 12 1<=T<=20 , 1<=A<=10000 , 1<=B<=10^{16} , 1<=N<=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=0∑51i=1∑n⌊2ka+b⋅i⌋−⌊2k+1a+b⋅i⌋∗2
先把从首位到第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 );
仔细对比,不难发现两者意义是相同的。

674

被折叠的 条评论
为什么被折叠?



