福州大学第十三届程序设计竞赛_重现解题报告

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/queuelovestack/article/details/51296950

此文章可以使用目录功能哟↑(点击上方[+])

都快要退役了,但是感觉自己还是实力不够,智商捉急...

链接→福州大学第十三届程序设计竞赛_重现

 Problem A Calculus Midterm

Accept: 0    Submit: 0
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

Yellowstar刚考完微积分期中考,微积分期中考卷一共三个部分,8道选择题每题3分,8道填空题,每题2分,10题答题每题6分,总分100分。Yellowstar考完期中考后已经失去了计算能力,他只记得每部分对的题数,现在想请你帮忙计算一下他的得分,能否通过考试。

 Input

多组数据,处理到EOF。

每组数据输入3个整数 x,y,z(0<=x<=8,0<=y<=8,0<=z<=10)分别表示选择题、填空题和简答题正确的题数。

 Output

每组数据需要输出两行,如果他最少的得分大于等于60 ,第一行输出“I passed the exam.”(双引号不需要输出),第二行输出他的得分;如果他最低得分小于60,第一行输出“Exam was too hard.”(双引号不需要输出),第二行输出他的得分。

 Sample Input

8 8 10
0 0 0

 Sample Output

I passed the exam.
100
Exam was too hard.
0

 Problem Idea

解题思路:水题,单纯判断3*x+2*y+6*z是否大于等于60

题目链接→Problem 2229 Calculus Midterm

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 100005;
const int M = 40;
const int inf = 100000000;
const int mod = 2009;
int main()
{
    int x,y,z,sum;
    while(~scanf("%d%d%d",&x,&y,&z))
    {
    	sum=x*3+y*2+z*6;
    	if(sum>=60)
    		puts("I passed the exam.");
   		else
   			puts("Exam was too hard.");
		printf("%d\n",sum);
    }
    return 0;
}


 Problem B 翻翻棋

Accept: 0    Submit: 0
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

象棋翻翻棋(暗棋)中双方在4*8的格子中交战,有时候最后会只剩下帅和将。根据暗棋的规则,棋子只能上下左右移动,且相同的级别下,主动移动到地方棋子方将吃掉对方的棋子。将和帅为同一级别。然而胜负在只剩下帅和将的时候已定。

 Input

第一行T,表示T组数据。

每组数据共有四行字符串,每行字符串共八个字符

’#’表示空格

’*’表示红方帅

’.’表示黑方将

此时红方先走

每组输入之间没有空行。

 Output

每组数据输出一行。若为红方赢输出Red win,否则输出 Black win

 Sample Input

1
######.#
#####*##
########
########

 Sample Output

Black win

 Problem Idea

解题思路:水题,判断曼哈顿距离(|x1-x2|+|y1-y2|)的奇偶性,若为奇数,先手必胜;若为偶数,先手必败

题目链接→Problem 2230 翻翻棋

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 100005;
const int M = 40;
const int inf = 100000000;
const int mod = 2009;
char s[4][15];
int main()
{
    int t,i,j,x1,y1,x2,y2,d;
    scanf("%d",&t);
    while(t--)
    {
    	for(i=0;i<4;i++)
    		scanf("%s",s[i]);
   		for(i=0;i<4;i++)
   			for(j=0;j<8;j++)
   				if(s[i][j]=='*')
   					x1=i,y1=j;
				else if(s[i][j]=='.')
					x2=i,y2=j;
		d=abs(x2-x1)+abs(y2-y1);
		if(d%2)
			puts("Red win");
		else
			puts("Black win");
    }
    return 0;
}

 Problem C 平行四边形数

Accept: 0    Submit: 0
Time Limit: 2000 mSec    Memory Limit : 32768 KB

 Problem Description

在一个平面内给定n个点,任意三个点不在同一条直线上,用这些点可以构成多少个平行四边形?一个点可以同时属于多个平行四边形。

 Input

多组数据(<=10),处理到EOF。

每组数据第一行一个整数n(4<=n<=500)。接下来n行每行两个整数xi,yi(0<=xi,yi<=1e9),表示每个点的坐标。

 Output

每组数据输出一个整数,表示用这些点能构成多少个平行四边形。

 Sample Input

4
0 1
1 0
1 1
2 0

 Sample Output

1

 Problem Idea

解题思路:给你n个点,求能构成多少个平行四边形。首先,根据平行四边形的一个定义:对角线平分的四边形为平行四边形

故而,我们可以求出任意两点的中点,由于题目说任意三个点都不在同一直线上,所以排除了以下情况:


那么,接下来要做的就是计算k对有共同中点的点集(每个点集包含两个点)能组成多少个平行四边形,即k(k-1)/2

题目链接→Problem 2231 平行四边形数

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 505;
const int M = 40;
const int inf = 100000000;
const int mod = 2009;
struct node
{
	int x,y;
}s[N],d[N*N];
bool cmp(node x,node y)
{
	if(x.x!=y.x)
		return x.x<y.x;
	return x.y<y.y;
}
int main()
{
    int n,i,j,p,c;
    while(~scanf("%d",&n))
    {
    	p=c=0;
    	for(i=0;i<n;i++)
	    	scanf("%d%d",&s[i].x,&s[i].y);
    	for(i=0;i<n;i++)
    		for(j=i+1;j<n;j++)
   			{
			   	d[p].x=s[i].x+s[j].x;
			   	d[p].y=s[i].y+s[j].y;
			   	p++;
		  	}
	  	sort(d,d+p,cmp);
	  	for(i=0,j=1;i<p&&j<p;i=j,j++)
  		{
		  	while(d[j].x==d[i].x&&d[j].y==d[i].y)
		  		j++;
	  		c+=(j-i-1)*(j-i)/2;
		}
		printf("%d\n",c);
    }
    return 0;
}

 Problem D 炉石传说

Accept: 0    Submit: 0
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

GG学长虽然并不打炉石传说,但是由于题面需要他便学会了打炉石传说。但是传统的炉石传说对于刚入门的GG学长来说有点复杂,所以他决定自己开发一个简化版的炉石传说。

在简化版的炉石传说中:

每个随从只有生命值和攻击力,并且在你的回合下,你的每只随从在本回合下只能选择一个敌方随从进行攻击。当两个随从a,b交战时,a的生命值将减去b的攻击力,b的生命值将减去a的攻击力,(两个伤害没有先后顺序,同时结算)。如果a或b的生命值不大于0,该随从将死亡。

某一次对局中,GG学长和对手场面上均有n个随从,并且是GG学长的回合。由于GG学长是个固执的boy,他一定要在本回合杀死对方所有随从,并且保证自己的随从全部存活。他想知道能否做到。

 Input

第一行为T,表示有T组数据。T<=100。

每组数据第一行为n,表示随从数量(1 <= n <= 100)

接下来一行2 * n个数字a1, b1, a2, b2, ... , an, bn (1 <= ai, bi <= 100)

表示GG学长的n个随从,ai表示随从生命,bi表示随从攻击力

接下来一行2 * n个数字c1, d1, c2, d2, ... , cn, dn (1 <= ci, di <= 100)

表示对手的n个随从,ci表示随从生命,di表示随从攻击力。

 Output

每组数据,根据GG是否能完成他的目标,输出一行”Yes”或”No”。

 Sample Input

2
3
4 4 5 5 6 6
1 1 2 2 3 3
3
4 4 5 5 6 6
1 4 2 4 3 4

 Sample Output

Yes
No

 Problem Idea

解题思路:因为GG学长和对手均有n个随从,GG学长先手,且每个随从只能攻击对方的一个随从,很明显是二分匹配问题,而解决二分匹配的问题,常常可以转化为网络流来做,那简单了,只要我们把图给建出来,剩下的就交给网络流模板了

首先,我们要添加一个源点1,和汇点2*n+2,源点1到点2~n+1都有一条流量为1的路,而点n+2~2*n+1到汇点2*n+2也都有一条流量为1的路

其次考虑点2~n+1与点n+2~2*n+1之间的情况,很明显,只有在GG学长的一个随从X攻击大于等于对方随从Y的血量,且GG学长的该随从X血量大于对方随从Y的攻击,这种情况才有一条从点X到点Y的流量为1的路

最后,我们只要判断该图的最大流是否为n即可

题目链接→Problem 2232 炉石传说

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")  
#include<stdio.h>  
#include<string.h>  
#include<stdlib.h>  
#include<queue>  
#include<stack>  
#include<math.h>  
#include<vector>  
#include<map>  
#include<set>  
#include<cmath>  
#include<string>  
#include<algorithm>  
#include<iostream>  
#define exp 1e-10  
using namespace std;  
const int N = 505;  
const int M = 1005;  
const int inf = 1000000007;  
const int mod = 2009;  
#include<iostream>  
#include<cstdio>  
#include<cstring>  
using namespace std;  
int Map[N][N];//存图  
int pre[N];//记录当前点的前驱  
int level[N];//记录距离标号  
int gap[N];//gap常数优化  
int NV,NE;  
//入口参数vs源点,vt汇点  
int SAP(int vs,int vt,int n)  
{  
    memset(pre,-1,sizeof(pre));  
    memset(level,0,sizeof(level));  
    memset(gap,0,sizeof(gap));  
    gap[0]=n;  
    int v,u=pre[vs]=vs,maxflow=0,aug=inf;  
    while(level[vs]<n)  
    {  
        //寻找可行弧  
        for(v=1;v<=n;v++)  
            if(Map[u][v]>0&&level[u]==level[v]+1)  
                break;  
        if(v<=n)  
        {  
            pre[v]=u;  
            u=v;  
            if(v==vt)  
            {  
                aug=inf;  
                //寻找当前找到的一条路径上的最大流  
                for(int i=v;i!=vs;i=pre[i])  
                    if(aug>Map[pre[i]][i])  
                        aug=Map[pre[i]][i];  
                maxflow+=aug;  
                //更新残留网络  
                for(int i=v;i!=vs;i=pre[i])  
                {  
                    Map[pre[i]][i]-=aug;  
                    Map[i][pre[i]]+=aug;  
                }  
                u=vs;//从源点开始继续搜  
            }  
        }  
        else  
        {  
            //找不到可行弧  
            int minlevel=n;  
            //寻找与当前点相连接的点中最小的距离标号  
            for(v=1;v<=n;v++)  
                if(Map[u][v]>0&&minlevel>level[v])  
                    minlevel=level[v];  
            gap[level[u]]--;//(更新gap数组)当前标号的数目减1;  
            if(gap[level[u]]==0)  
                break;//出现断层  
            level[u]=minlevel+1;  
            gap[level[u]]++;  
            u=pre[u];  
        }  
    }  
    return maxflow;  
} 
int a[N],b[N],c[N],d[N]; 
int main()  
{  
    int n,m,u,v,cap;  
    int t,i,j;
    scanf("%d",&t);
    while(t--)
    {
    	memset(Map,0,sizeof(Map));
    	scanf("%d",&n);
    	for(i=1;i<=n;i++)
    		scanf("%d%d",&a[i],&b[i]);
   		for(i=1;i<=n;i++)
    		scanf("%d%d",&c[i],&d[i]);
   		for(i=1;i<=n;i++)
   		{
   			Map[1][i+1]=1;
			for(j=1;j<=n;j++)
   			{
				if(a[i]>d[j]&&b[i]>=c[j])
   					Map[i+1][j+n+1]=1;
				Map[j+n+1][2*n+2]=1;  	
		   	}   	
	   	}  
	   	if(SAP(1,n*2+2,n*2+2)==n)
    		puts("Yes");
		else
			puts("No");
    }
	
    return 0;  
}  


 Problem E ~APTX4869

Accept: 0    Submit: 0
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

为了帮助柯南回到一米七四,阿笠博士夜以继日地研究APTX4869的解药。他得出了如下结果:

1.解药由n种原料构成;

2.对于两种不同的的原料a,b,它们之间有个影响值f(a,b);

3.需要把原料分成两个部分X,Y,每部分中至少有一种原料;

4.解药的效果由分别属于X,Y的原料之间,最小的影响值决定,即

效果=min{f(a,b)|a∈X,b∈Y)}

博士需要你帮忙求出:在所有的方案中,最大的效果值可以是多少?

 Input

多组数据(<=10),处理到EOF。

每组数据输入第一行为一个正整数n。

接下去是一个n行n列的整数矩阵,同一行的数以空格隔开。矩阵第i行j列表示第i种和第j种材料的影响值f(i,j)。给出的矩阵是对称的,即f(i,j)=f(j,i)。当i=j时,f(i,i)没有意义,矩阵该处的值为-1。

2<=n<=800。当i!=j时,0<=f(i,j)<=1000000;当i=j时,f(i,j)=-1。

 Output

每组数据输出一行,表示最大可能的效果值。

 Sample Input

3
-1 100 300
100 -1 200
300 200 -1

 Sample Output

200

 Problem Idea

解题思路:为了效果值能够尽可能大,我们应该避免选到小的f(i,j)

那换种思路,只要不存在相对较小的f(i,j),那我们就可以避免取到小的f(i,j)

那如何使相对较小的f(i,j)不存在呢?很简单,只要第i种材料与第j种材料属于同一类,那么第i种材料与第j种材料之间就不存在影响值

而我们需要做的就是,首先二分答案,然后将所有小于二分值的影响值f(i,j)中的两种材料划分到同一类,最后判断有几个连通块就行

判连通块的方法有很多种,本人采用并查集来实现

题目链接→Problem 2233 ~APTX4869

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 805;
const int M = 40;
const int inf = 100000000;
const int mod = 2009;
int n,s[N][N],c[N],b[N*N/2],p;
bool v[N];
int fun(int x)  
{  
    if(c[x]!=x)  
        c[x]=fun(c[x]);  
    return c[x];  
} 
void init()
{
	for(int i=1;i<=n;i++)
		c[i]=i;	
	memset(v,false,sizeof(v));
}
bool judge(int x)
{
	int i,j,k,t=0;
	init();
	for(i=1;i<=n;i++)
		for(j=i+1;j<=n;j++)
			if(s[i][j]<x)
				c[fun(i)]=fun(j); 
	for(i=1;i<=n;i++)
	{
		k=fun(i);
		if(!v[k])
		{
			t++;
			v[k]=true;
		}
	}
	if(t>1)
		return true;
	return false;
}
void Binary()//二分答案 
{
    int l=0,r=p-1,ans=0;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(judge(b[mid]))
		{
			l=mid+1;
			ans=max(ans,b[mid]);
		}
        else 
			r=mid;
    }
    if(l==r&&judge(b[l]))
    	ans=max(ans,b[l]);
    printf("%d\n",ans);
}
int main()
{
    int i,j;
    while(~scanf("%d",&n))
    {
    	p=0;
    	for(i=1;i<=n;i++)
    		for(j=1;j<=n;j++)
   			{
			   	scanf("%d",&s[i][j]);
			   	if(j>i)
				   	b[p++]=s[i][j];
		   	}
	   	sort(b,b+p);
	   	p=unique(b,b+p)-b;
		Binary();
    }
    return 0;
}
/*
3
-1 100 300
100 -1 200
300 200 -1
4
-1 200 200 100
200 -1 200 100
200 200 -1 200
100 100 200 -1
4
-1 200 100 100
200 -1 200 100
100 200 -1 200
100 100 200 -1
*/

 Problem F 牧场物语

Accept: 0    Submit: 0
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

小茗同学正在玩牧场物语。该游戏的地图可看成一个边长为n的正方形。

小茗同学突然心血来潮要去砍树,然而,斧头在小茗的右下方。

小茗是个讲究效率的人,所以他会以最短路程走到右下角,然后再返回到左上角。并且在路上都会捡到/踩到一些物品,比如说花朵,钱和大便等。

物品只能被取最多一次。位于某个格子时,如果格子上还有物品,就一定要取走。起点和终点上也可能有物品。

每种物品我们将为其定义一个价值,当然往返之后我们取得的物品的价值和越大越好。但是小茗同学正在认真地玩游戏,请你计算出最大的价值和。

 Input

多组数据(<=10),处理到EOF。

第一行输入正整数N(N≤100),表示正方形的大小。

接下来共N行,每行N个整数Ai,j(|Ai,j|≤10^9),表示相应对应位置上物品的价值。值为0表示没有物品。

 Output

每组数据输出一个整数,表示最大价值和。

 Sample Input

2
11 14
16 12

 Sample Output

53

 Problem Idea

解题思路:小茗从左上角走到右下角再走回左上角,这个问题可以转换为两个人同时从左上角到右下角,使得路上取得最大权值

显然,此题就变成了一道DP题

令ans[k][(x1,y1)][(x2,y2)]表示1号小茗第k步走到点(x1,y1),同时2号小茗第k步走到(x2,y2),两人取得的权值之和

很容易的,我们可以得到转移方程

ans[k][(x1,y1)][(x2,y2)]=max{

ans[k-1][(x1-1,y1)][(x2-1,y2)],

ans[k-1][(x1,y1-1)][(x2,y2-1)],

ans[k-1][(x1-1,y1)][(x2,y2-1)],

ans[k-1][(x1,y1-1)][(x2-1,y2)]

}

另外,题目说小茗会以最短路程走到右下角,那么,说明小茗从起点到终点需要k=n+n-2步

而k与坐标点(x,y)的关系为k+2=x+y,因为0步的时候小茗位于(0,0)

那么我们就可以将转移方程简化掉两维,用步数和横坐标来替换纵坐标,如此,转移方程为

ans[k][x1][x2]=max{

ans[k-1][x1-1][x2-1],

ans[k-1][x1][x2],

ans[k-1][x1-1][x2],

ans[k-1][x1][x2-1]

}

步数增加之后,即便横坐标不变,纵坐标必定是增加了的

此题另一个优化点是步数k

因为k状态只和k-1状态有关,我们可以利用滚动数组来进一步优化空间

有个注意的地方是s[i][j]的取值范围(<=10^9),故而初始化的时候要把-inf弄得小一些,1e12足够了,因为这个我还错了几次,供大家借鉴

题目链接→Problem 2234 牧场物语

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 105;
const int M = 40;
const __int64 inf = 1e12;
const int mod = 2009;
int s[N][N];
__int64 ans[2][N][N];
int main()
{
    int n,i,j,k,c;
    while(~scanf("%d",&n))
    {
    	for(i=0;i<=n;i++)
    		for(j=0;j<=n;j++)
   			{
   				ans[0][i][j]=ans[1][i][j]=-inf;
   				if(i!=0&&j!=0)
					scanf("%d",&s[i][j]);   	
		   	}
	   	ans[0][1][1]=s[1][1];
	   	for(c=0,k=1;k<2*n-1;k++)
   		{
   			c^=1;
		   	for(i=1;i<=min(k+1,n);i++)
    			for(j=1;j<=min(k+1,n);j++)
   				{
					ans[c][i][j]=max(max(ans[c^1][i][j],ans[c^1][i-1][j-1]),max(ans[c^1][i-1][j],ans[c^1][i][j-1])); 
					if(i==j)
						ans[c][i][j]+=s[i][k+2-i];
					else
						ans[c][i][j]+=s[i][k+2-i]+s[j][k+2-j];
			   	}
	   	}
	   	printf("%I64d\n",ans[c][n][n]);
    }
    return 0;
}

 Problem G 国王的出游

Accept: 0    Submit: 0
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

黑暗之王有一片棋盘般的疆土,这片疆土有2*10^9行,有2*10^9列。假设这块疆土的行从上到下编号1到2*10^9,它的列从左到右也编号1到2*10^9。我们可以把第i行第j列的方格记为坐标(i,j)。

但是这偌大棋盘只有被给的N个线状区域才是允许通行的。每个线状区域被三个参数描述,ri,ai,bi(ai<= bi)。ri代表给定线状区域的行编号,ai代表线状区域的起始列编号,bi代表末尾编号。

现在国王想要从一个给定坐标方格(x0,y0)通过最小移动步数到达终点坐标方格(x1,y1),而且只能通过上述给出的允许通行的线状区域。

国王移动一步只能发生在相邻的方格之间。此外,如果两个方格至少共享一个点我们便认为他们相邻。

 Input

有多组数据(<=30),处理到文件尾(EOF)。

每组数据第一行包含四个整数,x0, y0, x1, y1(1 <= x0, y0, x1, y1 <= 2*10^9),分别代表国王的初始坐标和终点坐标。

第二行有一个整数N (1 <= N <= 10^5),代表有N条可通行的线状区域。

接下里会有N行,第i行包含三个整数,ri,ai, bi (1 <= ri, ai bi <= 2*10^9),含义看题面。

数据允许线状区域会有交叉或者嵌套。

数据保证国王的起点方格和终点方格都是可通行的,并且两个区域不相同。另外保证所有线状区域的的长度总和不超过10^5。

1 <= x0, y0, x1, y1, ri, ai, bi<= 2*10^9,1 <= N <= 10^5

 Output

如果没有一条道路使得国王从起始区域到达终点区域,则输出 -1。

否则,则输出国王的从起始区域到达终点区域的最小步数。

 Sample Input

5 7 6 11
3
5 3 8
6 7 11
5 2 5

1 1 2 10
2
1 1 3
2 6 10

 Sample Output

4
-1

 Problem Idea

解题思路:一开始相信绝大多数人都会被这棋盘的规模吓一跳,2*10^9 * 2*10^9,然后束手无策,但是只要仔细读题,就会发现其中的入手点

题目保证所有线状区域的的长度总和不超过10^5,原来之前的都是虚张声势

那么,题目就容易下手了,相信绝大多数人这个时候应该都能想到bfs(广搜)

但是难点在于如何保存已经走过的格子,因为坐标规模比较大,之前的visit[][]数组必然是已经开不出来

那我们可以转换一下思路,我最先想到的是用STL里的map来实现

TLE代码(下面有正确代码,往下看)如下:

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 100005;
const int M = 40;
const int inf = 100000000;
const int mod = 2009;
struct node
{
	int x,y,c;
	node(){}
	node(int x0,int y0,int c0):x(x0),y(y0),c(c0){}
};
int g[8][2]={{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1},{0,1},{1,1}};
map<pair<int,int>,bool> m;
int bfs(int x0,int y0,int x1,int y1)
{
	int i;node u;
	queue<node> q;
	q.push(node(x0,y0,0));
	m[make_pair(x0,y0)]=false;
	while(!q.empty())
	{
		u=q.front();
		q.pop();
		for(i=0;i<8;i++)
		{
			x0=u.x+g[i][0];
			y0=u.y+g[i][1];
			if(x0==x1&&y0==y1)
                return u.c+1;
			if(m[make_pair(x0,y0)])
			{
				m[make_pair(x0,y0)]=false;
				q.push(node(x0,y0,u.c+1));
			}
		}
	}
	return -1;
}
int main()
{
	int x0,y0,x1,y1,n,i,r,a,b;
	while(~scanf("%d%d%d%d",&x0,&y0,&x1,&y1))
	{
		m.clear();
		scanf("%d",&n);
		while(n--)
		{
			scanf("%d%d%d",&r,&a,&b);
			for(i=a;i<=b;i++)
				m[make_pair(r,i)]=true;
		}
		if(x0==x1&&y0==y1)
            puts("0");
        else
            printf("%d\n",bfs(x0,y0,x1,y1));
	}
    return 0;
}

题目链接→Problem 2235 国王的出游

当然这个想法解决单组已经足够了(详情见CodeForces 242C),但是对于T(<=30)还有点力不从心,原因在于每次的map搜索还是比较耗时的,那我们再进行进一步的优化,考虑将能走的格子坐标保存下来,每次二分来找判断该点是否能走,这样便成功得以解决,正确代码如下:

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 100005;
const int M = 40;
const int inf = 100000000;
const int mod = 2009;
struct node
{
	int x,y,c;
	node(){}
	node(int x0,int y0,int c0):x(x0),y(y0),c(c0){}
};
int p,g[8][2]={{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1},{0,1},{1,1}};
pair<int,int> m[N];
bool v[N];
int bfs(int x0,int y0,int x1,int y1)
{
	int i,k;node u;
	queue<node> q;
	q.push(node(x0,y0,0));
	k=lower_bound(m,m+p,make_pair(x0,y0))-m;
	if(m[k]==make_pair(x0,y0))
		v[k]=false;
	while(!q.empty())
	{
		u=q.front();
		q.pop();
		for(i=0;i<8;i++)
		{
			x0=u.x+g[i][0];
			y0=u.y+g[i][1];
			if(x0==x1&&y0==y1)
                return u.c+1;
   			k=lower_bound(m,m+p,make_pair(x0,y0))-m;
			if(m[k]==make_pair(x0,y0)&&v[k])
			{
				v[k]=false;
				q.push(node(x0,y0,u.c+1));
			}
		}
	}
	return -1;
}
int main()
{
	int x0,y0,x1,y1,n,i,r,a,b;
	while(~scanf("%d%d%d%d",&x0,&y0,&x1,&y1))
	{
		p=0;
		memset(v,false,sizeof(v));
		scanf("%d",&n);
		while(n--)
		{
			scanf("%d%d%d",&r,&a,&b);
			for(i=a;i<=b;i++)
			{
				m[p]=make_pair(r,i);
				v[p++]=true;
			}
		}
		sort(m,m+p);
		p=unique(m,m+p)-m;
		if(x0==x1&&y0==y1)
            puts("0");
        else
            printf("%d\n",bfs(x0,y0,x1,y1));
	}
    return 0;
}


 Problem H 第十四个目标

Accept: 0    Submit: 0
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

目暮警官、妃英里、阿笠博士等人接连遭到不明身份之人的暗算,柯南追踪伤害阿笠博士的凶手,根据几起案件现场留下的线索发现凶手按照扑克牌的顺序行凶。在经过一系列的推理后,柯南发现受害者的名字均包含扑克牌的数值,且扑克牌的大小是严格递增的,此外遇害者与毛利小五郎有关。

为了避免下一个遇害者的出现,柯南将可能遭到暗算的人中的数字按关联程度排列了出来,即顺序不可改变。柯南需要知道共有多少种可能结果,满足受害人名字出现的数字严格递增,但是他柯南要找出关键的证据所在,所以这个任务就交给你了。

(如果你看不懂上面在说什么,这题是求一个数列中严格递增子序列的个数。比如数列(1,3,2)的严格递增子序列有(1)、(3)、(2)、(1,3)、(1,2),共5个。长得一样的但是位置不同的算不同的子序列,比如数列(3,3)的答案是2。)

 Input

多组数据(<=10),处理到EOF。

第一行输入正整数N(N≤100 000),表示共有N个人。

第二行共有N个整数Ai(1≤Ai≤10^9),表示第i个人名字中的数字。

 Output

每组数据输出一个整数,表示所有可能的结果。由于结果可能较大,对1 000 000 007取模后输出。

 Sample Input

3
1 3 2

 Sample Output

5

 Problem Idea

解题思路:找数列中严格递增子序列的个数

记a[i]表示结尾为i的序列个数,考虑到Ai会达到10^9,故而先离散化

然后,我们要计算a[i],必须知道所有的a[j](j<i)之和,这时,可以考虑用树状数组

因为树状数组c[]表示的正是前多少项之和,详情见  链接→树状数组学习笔记

当然也可以自己写线段树

题目链接→Problem 2236 第十四个目标

 Source Code

/*Sherlock and Watson and Adler*/
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<complex>
#include<string>
#include<algorithm>
#include<iostream>
#define exp 1e-10
using namespace std;
const int N = 100005;
const int M = 40;
const int inf = 100000000;
const int mod = 1000000007;
int s[N],a[N],n;
__int64 c[N];
int lowbit(int t)  
{//计算c[t]展开的项数     
    return t&(-t);    
}  
void update(int i,__int64 x)  
{  
    while(i<=n)  
    {  
        c[i]=(c[i]+x)%mod;   
        i+=lowbit(i);   
    }  
}  
__int64 Sum(int n) //求前n项的和.       
{  
    __int64 sum=0;   
    while(n>0)  
    {  
         sum=(sum+c[n])%mod;   
         n-=lowbit(n);  
    }  
    return sum;  
}  
int main()
{
    int i,j,k;
    __int64 ans;
    while(~scanf("%d",&n))
    {
    	memset(c,0,sizeof(c));
    	ans=0;
    	for(i=0;i<n;i++)
    	{
	    	scanf("%d",&s[i]);
	    	a[i]=s[i];
	    }
	    sort(a,a+n);
	    k=unique(a,a+n)-a;
	    for(i=0;i<n;i++)
	    	s[i]=lower_bound(a,a+k,s[i])-a+1;
	   	/*for(i=0;i<n;i++)
			printf("%d ",s[i]);*/
		for(i=0;i<n;i++)
			update(s[i],Sum(s[i]-1)+1);
		/*for(i=1;i<=n;i++)
			printf("%I64d ",c[i]);*/
	    printf("%I64d\n",Sum(n));
    }
    return 0;
}

菜鸟成长记

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页