纪中DAY4做题小结

T1:输油管道

Description
  请你帮忙设计一个从城市M到城市Z的输油管道,现在已经把整个区域划分为R行C列,每个单元格可能是空的也可能是以下7种基本管道之一:
  图1
  油从城市M流向Z,‘+’型管道比较特殊,因为石油必须在两个方向(垂直和水平)上传输,如下图所示:
  图2
  现在恐怖分子弄到了输油管道的设计图,并把其中一个单元格中的管道偷走了,请你帮忙找到偷走的管道的位置以及形状。

Input
  第一行包含两个整数R和C(1<=R,C<=25)。
  接下来R行每行C个字符描述被偷之后的形状,字符分为以下三种:
  (1)‘.’表示空;
  (2)字符‘|’(ASCII为124)、‘-’、‘+’、‘1’、‘2’、‘3’、‘4’描述管道的形状;
  (3)‘M’和‘Z’表示城市,两个都是只出现一次。
  输入保证石油的流向是唯一的,只有一个管道跟M和Z相连,除此此外,保证没有多余的管道,也就是说所有的管道在加进被偷的管道后一定都会被用上。
  输入保证有解而且是唯一的。

Output
  输出被偷走的管道的行号和列号以及管道的类型。

题目链接测试数据格式不好调,想看的自己去看吧

简要思路:这是一道技术含量低但是非常考验细心程度的一道模拟题,很多人都用建图在路上跑的方法,但我是直接用染色法找本应在输油轨迹上却没有管道的点,最后要注意特判起点终点都没路(形如M.Z)的情况。

//很长但不慢
//看不顺眼请轻喷
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 30;
char ss[N];
int map[N][N];
int col[N][N];
int xx[5] = { 0 , 1 , -1 , 0 , 0 };
int yy[5] = { 0 , 0 , 0 , 1 , -1 };
int n , m;
int main () {
	scanf("%d",&n);
	scanf("%d",&m);
	int tot = 0;
	int x1 , x2 , y1 , y2;
	memset( col , 0 , sizeof(col) );
	for ( int i = 1 ; i <= n ; ++i ) {
		scanf("%s",ss);
		for ( int j = 1 ; j <= m ; ++j ) {
			switch( ss[j - 1] ) {
				case '.' :{
					map[i][j] = 0;
					break;
				}
				case '1' :{
					map[i][j] = 1;
					col[i + 1][j] = 1;
					col[i][j + 1] = 1;
					++tot;
					break;
				}
				case '2' :{
					map[i][j] = 2;
					col[i - 1][j] = 1;
					col[i][j + 1] = 1;
					++tot;
					break;
				}
				case '3' :{
					map[i][j] = 3;
					col[i - 1][j] = 1;
					col[i][j - 1] = 1;
					++tot;
					break;
				}
				case '4' :{
					map[i][j] = 4;
					col[i][j - 1] = 1;
					col[i + 1][j] = 1;
					++tot;
					break;
				}
				case '|' :{
					map[i][j] = 5;
					col[i + 1][j] = 1;
					col[i - 1][j] = 1;
					++tot;
					break;
				}
				case '-' :{
					map[i][j] = 6;
					col[i][j + 1] = 1;
					col[i][j - 1] = 1;
					++tot;
					break;
				}
				case '+' :{
					map[i][j] = 7;
					col[i][j + 1] = 1;
					col[i + 1][j] = 1;
					col[i][j - 1] = 1;
					col[i - 1][j] = 1;
					++tot;
					break;
				}
				case 'M':{
					map[i][j] = 8;
					x1 = i;
					y1 = j;
					break;
				}
				case 'Z':{
					map[i][j] = 8;
					x2 = i;
					y2 = j;
					break;
				}
			}
		}
	}
	int sx , sy;
	//ans = ans * 10 + k;
	if ( !tot ) {
		if ( x1 == x2 ) {
			sx = x1;
			sy = ( y1 + y2 ) / 2;
			printf("%d %d ",sx,sy);
			printf("-");
		} else if ( y1 == y2 ) {
			sy = y1;
			sx = ( x1 + x2 ) / 2;
			printf("%d %d ",sx,sy);
			printf("|");
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = 1 ; j <= m ; ++j ) {
			if ( map[i][j] == 0 && col[i][j] == 1 ) {
				sx = i;
				sy = j;
				printf("%d %d ",sx,sy);
				int ans = 0;
				for ( int k = 1 ; k <= 4 ; ++k ) {
					if ( map[sx + xx[k]][sy + yy[k]] != 0 ) {
						int co = map[sx + xx[k]][sy + yy[k]];
						int cc = col[sx + xx[k]][sy + yy[k]];
						switch(k) {
							case 1:{
								if ( co == 2 || co == 3 || co == 5 || co == 7 || ( co == 8 && !cc )  ) {
									ans = ans * 10 + k;
								}
								break;
							}
							case 2:{
								if ( co == 1 || co == 4 || co == 5 || co == 7 || ( co == 8 && !cc ) ) {
									ans = ans * 10 + k;
								}
								break;
							}
							case 3:{
								if ( co == 3 || co == 4 || co == 6 || co == 7 || ( co == 8 && !cc ) ) {
									ans = ans * 10 + k;
								}
								break;
							}
							case 4:{
								if ( co == 1 || co == 2 || co == 6 || co == 7 || ( co == 8 && !cc ) ) {
									ans = ans * 10 + k;
								}
								break;
							}
						}
					}
				}
				//cout << ans << endl;
				switch( ans ) {
					case 13: {
						printf("1");
						break;
					}
					case 23: {
						printf("2");
						break;
					}
					case 24: {
						printf("3");
						break;
					}
					case 14: {
						printf("4");
						break;
					}
					case 12: {
						printf("|");
						break;
					}
					case 34: {
						printf("-");
						break;
					}
					case 1234: {
						printf("+");
						break;
					}
				}
				return 0;
			}
		}
	}
	return 0;
}

T2:数码问题

Description
  Alice有一个N*N的格子,把1-N^2按照从上到下从左到右的顺序填进表格中,允许在表格上进行两种操作:
  (1) 旋转行——这一行的数向右移动一个位置,而最后一列的数会移到第一列;
  (2) 旋转列——这一列的数向下移动一个位置,最后一行的数会移到第一行。
  Alice想把数X移到(R,C)处可以采用以下方法(分先后):
  •如果X不在C这一列,通过旋转行操作把X移到C这一列;
  •如果X不在R这一行,通过旋转列操作把X移到R这一行。
  下面是一个把6移到(3,4)的例子:
  图三
  Alice现在想采用上述方法,依次把K个数移到各自的目标位置,编程计算每个数需要几次操作。

Input
  第一行包含两个整数N(12<=N<=10000)和K(1<=K<=1000)。
  接下来K行,每行包含三个整数X(1<=X<=N^2)、R和C(1<=R,C<=N),描述需要移动的数以及目标位置。
  Alice必须按照输入顺序依次移动。

Output
  输出K行,每行输出一个整数,表示操作次数。

Sample Input
输入1:
4 1
6 3 4
输入2:
4 2
6 3 4
6 2 2
输入3:
5 3
1 2 2
2 2 2
12 5 5

Sample Output
输出1:
3
输出2:
3
5
输出3:
2
5
3

简要思路:本题对空间要求高,尽量不把空间浪费在无用的数据上,只用数组保存当前操作行与列移动的数目与受到影响的行与列(受影响的行是当前被移动的数所在行,受影响的列是目标列);每次操作时归纳先前操作对被移动的数的位置所造成的影响,这样,第一个数计算一次,第二个数计算二次,…,第k个数计算k次,时间复杂度是o(k2),完全可以接受。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int K = 1e3 + 5;
int n , k;
int x[K] , y[K] , dx[K] , dy[K];
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);
	int num , sx , sy;
	for ( int i = 1 ; i <= k ; ++i ) {
		read(num);
		read(sx);
		read(sy);
		int xx = (ceil)( (double)num / (double)n );//初始行 
		int yy = num % n;//初始列 
		if ( !yy ) {
			yy = n;
		}
		for ( int j = 1 ; j <= i - 1 ; ++j ) {//归纳先前操作的影响 
			if ( xx == x[j] ) {
				yy = ( yy + dy[j] ) % n;
				if ( !yy ) {
					yy = n;
				}
			}
			if ( yy == y[j] ) {
				xx = ( xx + dx[j] ) % n;
				if ( !xx ) {
					xx = n;
				}
			}
		}
		dx[i] = sx - xx;
		if ( dx[i] < 0 ) {
			dx[i] += n;
		}
		dy[i] = sy - yy;
		if ( dy[i] < 0 ) {
			dy[i] += n;
		}
		x[i] = xx;
		y[i] = sy;
		printf("%d\n",dx[i] + dy[i]);
	}
	return 0;
}

T3:灌水

Description
  学生都很喜欢灌水,第一天只有Alice给她的每个朋友灌了一次水,从第二天开始,所有学生(包括Alice)将会有规律地去灌水:
  •如果前一天被灌了奇数次的水,他们将会给每个朋友灌一次水;
  •如果前一天被灌了偶数次的水,他们将会给每个朋友灌两次水。
  学生编号为1到N,Alice为1号,学生之间的朋友关系会给出。
  计算H天后一共灌了几次水。

Input
  输入一行包含两个整数N和H(1<=N<=20,1<=H<=10^9),表示学生数和天数。
  接下来N行,每行包含N个‘0’或‘1’,(A,B)处的字符表示A和B的关系,‘1’表示是朋友关系,‘0’表示不是。注意自己和自己不是朋友关系,输入保证该矩阵是对称的。

Output
  输出H天后一共灌水的数量。

Sample Input
输入1:
4 1
0110
1001
1001
0110
输入2:
4 2
0110
1001
1001
0110
输入3:
5 3
01000
10110
01000
01001
00010

Sample Output
输出1:
2
输出2:
14
输出3:
26

Hint
【样例解释】
  样例2中,第一天Alice灌了2次水,第二天学生1和学生4给学生2和学生3都灌了2次水,而学生2和学生3给学生1和学生4各灌水1次,2天一共灌了14次水。
【数据范围】
  50%的数据 H<=1000。

简要思路:本题难在天数多,但矩阵规模小使状压成为可能,将奇点设为1,偶点设为0,按照从1到n的顺序进行状压。通过状压在天数多时发现规律从而减小计算量,细节多,调试难度较高,出现问题建议重写,我就曾经这样悲摧 (PS:路径保存最好使用邻接表,统计灌水数更方便)。

#include <iostream>
#include <cstdio>
#include <cstdio>
#define ll long long
using namespace std;
ll f[1050005] , w[1050005] , d[25][25] , er[25];
ll n , h , t , res , ans , pre;
bool pd = false;
char ss[25];
int main () {
	//freopen("data10.in","r",stdin);
	scanf("%lld",&n);
	scanf("%lld",&h);
	for ( int i = 1 ; i <= n ; ++i ) {
		scanf("%s",ss);
		for ( int j = 1 ; j <= n ; ++j ) {
			if ( ss[j - 1] == '1' ) {
				d[i][++d[i][0]] = j;
			}
		}
	}
	er[1] = 1;
	for ( int i = 2 ; i <= n ; ++i ) {
		er[i] = er[i - 1] * 2;//预处理,为状压铺路 
	}
	t = 0;
	for ( int i = 1 ; i <= d[1][0] ; ++i ) {
		t += er[d[1][i]];
	}
	res = d[1][0];
	w[t] = res;
	f[t] = 1;
	for ( int k = 2 ; k <= h ; ++k ) {
		pre = t;
		t = 0;
		for ( int i = 1 ; i <= n ; ++i ) {
			if ( pre & er[i] ) {
				for ( int j = 1 ; j <= d[i][0] ; ++j ) {
					if ( t & er[d[i][j]] ) {
						t -= er[d[i][j]];
					} else {
						t += er[d[i][j]];
					}
				}
				res += d[i][0];
			} else {
				res += 2 * d[i][0];
			}
		}
		if ( !f[t] ) {
			f[t] = k;
			w[t] = res;
		} else {//发现规律 
			ans = w[t] + ( h - f[t] ) / ( k - f[t] ) * ( res - w[t] );
			h = ( h - f[t] ) % ( k - f[t] );
			pd = true;
			break;
		}
	}
	if ( pd ) {
		res = 0;
		for ( int k = 1 ; k <= h ; ++k ) {
			pre = t;
			t = 0;
			for ( int i = 1 ; i <= n ; ++i ) {
				if ( pre & er[i] ) {
					for ( int j = 1 ; j <= d[i][0] ; ++j ) {
						if ( t & er[d[i][j]] ) {
							t -= er[d[i][j]];
						} else {
							t += er[d[i][j]];
						}
					}
					res += d[i][0];
				} else {
					res += 2 * d[i][0];
				}
			}
		}
	}
	printf("%lld",ans + res);
	return 0;
}

T4:开花

Description
  在遥远的火星上,上面的植物非常奇怪,都是长方形的,每个植物用三个数来描述:左边界L、右边界R以及高度H,如下图所示描述一个植物:L=2,R=5和H=4。
  图4
  每天都有一个新植物长出来,第一天的植物高度为1,后面每天长出的植物比前一天的高1。
  当一个新植物长出来的时候,跟其他植物的水平线段相交处会长出一朵小花(前提是之前没有长出花朵),如果线段交于端点,是不会长花的。
  下图为样例1的示意图:
  图5
  给出每天的植物的坐标,计算每天长出多少新花。

Input
  第一行包含一个整数N(1<=N<=100000),表示天数。
  接下来N行,每行两个整数L和R(1<=L<=R<=100000),表示植物的左右边界。

Output
  输出每天长出新植物后增加新花的数量。

Sample Input
输入1:
4
1 4
3 7
1 6
2 6
输入2:
5
1 3
3 5
3 9
2 4
3 8

Sample Output
输出1:
0
1
1
2
输出2:
0
0
0
3
2

简要思路:本题很明显要区间统计,一般用线段树,但利用差分思想也可用树状数组(编程难度更低 ),同时要注意开过花的地方不能再开花(良心样例 ),用一个数组统计某个点已开花的次数。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
ll tree[N] , sum[N];
int 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;
}
inline int lowbit( int x ) {
	return x & ( -x );
}
inline ll query( int x ) {
	ll ans = 0;
	for ( ; x ; x -= lowbit(x) ) {
		ans += tree[x];
	}
	return ans;
}
inline void update( int x , int val ) {
	for (  ; x <= N ; x += lowbit(x) ) {
		tree[x] += (ll)val;
	}
	return;
}
int main () {
	//freopen( "data10 (1).in" , "r" , stdin );
	read(n);
	int l , r;
	ll res = 0;
	ll ln , rn;
	for ( int i = 1 ; i <= n ; ++i ) {
		read(l);
		read(r);
		res = 0;
		ln = query(l);
		rn = query(r);
		//printf("%lld  %lld\n",ln,rn);
		res = ln + rn - sum[l] - sum[r];
		if ( ln ) {
			sum[l] = ln;
		}
		if ( rn ) {
			sum[r] = rn;
		}
		update( l + 1 , 1 );
		update( r , -1 );
		printf("%lld\n",res);
	}
	return 0;
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值