The ACM - ICPC is a worldwide programming challenge for undergraduates. The contest is composed of a Problem Set containing 6 to 10 algorithmic problems. It is to be solved within 5 hours by teams of three guys and only one computer per team. I participated two times with two different teams to the South West European Regional Contest and we ranked French runner up in 2005. The level was so high that, though we beat last year winners, we only scored 12th in the european final rankings (out of 55).
There is only one method to become better : train yourself on the Universidad de Valladolid Online Judge (here is my ID).
But, if you are blocked, here are some useful ressources I wrote to help:
Basis:
Graphs:
Groups:
Number Theory:
Computational Geometry:
Big Integers:
Useful Links:
Formatted input is the beginning of almost all problems : You must read the standard input to get the data. There are two cases : on one hand the data representation is dependant of the end of lines, on the other it is not. To know if this is dependant or not, try mentally squeezing all the newlines. If the input has lost some sense then it is dependant. eg: A list of numbers is clearly independant, a list of cities name is dependant because there can be cities with a blank in it and then you can't decide what's a city and what's not ("Paris New York London"), a list of of lists with the number of elements precised for each list is independant, ...
If it's independant, it's easier and the good solution is the scanf() function. For multiple entry problems, it can often be used like this:
while((scanf("%d",&n)==1)&&(n!=0)) {;}
If it is dependant, remember 4 things:
My solution for multiple entry problem is doing something like this:
for(gets(string);!feof(stdin);gets(string)) {
if (string[strlen(string)-1]=='\r')
string[strlen(string)-1]=0;
;
}
By the way, beware of multiple input problems, signaled by a blue or green comma!
An efficient sort is required in a large number of problems, so be sure to know at least one! The standard C library "stdlib.h" proposes the Quicksort O(n * ln(n)) in average. This is how to use it (of course, you can change the int type by any type you like...):
int compare(const void*a,const void*b){
const int * i=(const int *)a;
const int * j=(const int *)b;
if (*i<*j)
return -1;
else if (*i>*j)
return 1;
else
return 0;
}
qsort(array pointer,number of elements,size of elements, compare);
pointer=(int*)malloc(n*sizeof(int)); free(pointer);
int dfs(int vertex){
mark this vertex.
dfs all sons of this vertex.
}
int main(){
wipe all the marks on all vertices.
for all non marked vertices, call dfs.
}
It has some interesting properties, like each time you call dfs on a vertex, it will call dfs on all vertices of the same connected component. Doing something on a vertex after having called all sons of this vertex is called doing something in the DFS post order. It is useful to ensure we begin to process all leaves, then the small nodes and then the bigger ones.
head=0;
queue=0;
wipe all marks;
for(FIFO[queue++]=root;head!=queue;head++){
if FIFO[head] is not marked
mark FIFO[head];
for all not marked i sons of FIFO[head]{
mark sons[FIFO[head]][i];
FIFO[queue++]=sons[FIFO[head]][i];
}
printf("%d",FIFO[head]);
}
The interesting property of DFS is that is gives the Shortest Path between the root and any node in an unweighted graph.
int* graph_shortestpath(int results[GRAPH_MAXVERTEX],graph* graphe,int root){
int i,j,realmin,locrealmin,min;
for(i=0;i<graphe->nvertex;i++)
results[i]=-1;
results[root]=-2;
while (1){
locrealmin=-1;
realmin=GRAPH_MAXINT;
for(i=0;i<graphe->nvertex;i++)
if (results[i]==-2){
min=GRAPH_MAXINT;
if (graphe->matrix[i][root]!=-1)
min=graphe->matrix[i][root];
for (j=0;j<graphe->nvertex;j++)
if ((graphe->matrix[i][j]!=-1)&&(results[j]>=0)&&(results[j]+graphe->matrix[i][j]<min))
min=results[j]+graphe->matrix[i][j];
if (min<realmin){
realmin=min;
locrealmin=i;
}
}
if (locrealmin==-1)
break;
results[locrealmin]=realmin;
for(i=0;i<graphe->nvertex;i++)
if ((results[i]==-1)&&(graphe->matrix[i][locrealmin]!=-1))
results[i]=-2;
}
results[root]=0;
return results;
}
Note that it does not work if there are negative weights since the shortest way will be a negative infinity and that it returns -1 for unconnected vertices.
void graph_connected_dfs(int results[GRAPH_MAXVERTEX],graph* graphe,int vertex,int ncomponents,int* graph_fog){
int i;
graph_fog[vertex]=1;
results[vertex]=ncomponents;
for(i=0;i<graphe->nvertex;i++)
if ((graphe->matrix[vertex][i]>0)&&(graph_fog[i]==0))
graph_connected_dfs(results,graphe,i,ncomponents,graph_fog);
}
int graph_connected(int results[GRAPH_MAXVERTEX],graph* graphe){
graph temp;
int i,post[GRAPH_MAXVERTEX],ncomponents, graph_fog[GRAPH_MAXVERTEX];
graph_post(post,graph_transpose(&temp,graphe));
memset(graph_fog,0,graphe->nvertex*sizeof(int));
memset(results,0,graphe->nvertex*sizeof(int));
ncomponents=0;
for(i=graphe->nvertex-1;i>=0;i--)
if (graph_fog[post[i]]==0)
graph_connected_dfs(results,graphe,post[i],ncomponents++,graph_fog);
return ncomponents;
}
n=3;
for(i=0; i < (int)pow(2,n) ;i++){
for(j=0; j < n ;j++)
if (i&(1 << j))
printf("%d ",j+1);
printf("\n");
}
int integer_factor(int *prime,int *mult,int n){
int i,p;
memset(prime,0,INTEGER_MAXFACTORS*sizeof(int));
memset(mult,0,INTEGER_MAXFACTORS*sizeof(int));
p=0;
while ((n%2==0)&&(n!=0)) {
mult[0]++;
prime[0]=2;
p=1;
n>>=1;
}
for(i=3;i<=sqrt(n)+1;i+=2)
while (n%i==0) {
if (prime[p-1]!=i)
prime[p++]=i;
mult[p-1]++;
n=n/i;
}
if (n>1){
prime[p++]=n;
mult[p-1]++;
}
return p;
}
int integer_gcd(int a,int b) {
int r;
while (b!=0){
r=a%b;a=b;b=r;
}
return a;
}
typedef struct {
double X;
double Y;
} point;
int point_equal(point a, point b){
return ((fabs(a.X-b.X) < GEOM_EPSILON)&&(fabs(a.Y-b.Y) < GEOM_EPSILON));
}
Another really important function is knowing if three points are aligned and you can do this this way:
int point_colinear(point p1,point p2,point p3){
return (fabs((p1.X-p2.X)*(p1.Y-p3.Y)-(p1.Y-p2.Y)*(p1.X-p3.X)) < GEOM_EPSILON);
}
Last but not least, it is often useful to know if three points are clockwise or counterclockwise, but beware of colinear points!
double triangle_signedarea(point a, point b, point c){
return(a.X*b.Y - a.Y*b.X + b.X*c.Y - b.Y*c.X + c.X*a.Y - c.Y*a.X)/2.0;
}
int triangle_iscounterclockwise(point a, point b, point c){
return (triangle_signedarea(a,b,c) > -GEOM_EPSILON);
}
1. Skim all the duplicates, they are dangerous and not useful.
2. Get the leftlowest point. It is clearly in the hull.
3. Sort the other points by growing angles around the leftlowest point.
4. Add points one by one to the hull. If the triangle formed by the last three hull point is turning in a wrong direction, remove the middle point that is clearly not in the hull. Be specially aware of colinear points. You can use a cyclic definition or a sentinel to take care of the last point.
for(i=0;i<=input->npoints-1;i++)
if ((!point_colinear(A,input->points[i],input->points[(i+1)%input->npoints]))&&(!triangle_iscounterclockwise(A,input->points[i],input->points[(i+1)%input->npoints])))
return FALSE;
return TRUE;
There is a tricky and useful formula you better remember to get the area of a polygon.:
float area=0;
for (k=0;k < npoints;k++)
area+=points[k].X*points[(k+1)%npoints].Y-points[k].Y*points[(k+1)%npoints].X;
return fabs(area/2.0);
Sometimes, integers will not fit into 32 bits, so you will need to program it. How to handle additions, substractions, multiplications and divisions for really big integers? The answer is simple, do it like you always did on the paper. The code I use is a slightly different version of the one given in the Programming Challenges book from S. Skienna (by the way, you should buy it cause it is a good book), so I won't give the implementation to you.