T1:输油管道
Description
请你帮忙设计一个从城市M到城市Z的输油管道,现在已经把整个区域划分为R行C列,每个单元格可能是空的也可能是以下7种基本管道之一:
油从城市M流向Z,‘+’型管道比较特殊,因为石油必须在两个方向(垂直和水平)上传输,如下图所示:
现在恐怖分子弄到了输油管道的设计图,并把其中一个单元格中的管道偷走了,请你帮忙找到偷走的管道的位置以及形状。
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。
每天都有一个新植物长出来,第一天的植物高度为1,后面每天长出的植物比前一天的高1。
当一个新植物长出来的时候,跟其他植物的水平线段相交处会长出一朵小花(前提是之前没有长出花朵),如果线段交于端点,是不会长花的。
下图为样例1的示意图:
给出每天的植物的坐标,计算每天长出多少新花。
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;
}