T1:小C的倍数问题(hdu6108)
题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6108
题目分析:最水的一题,考场上最多人AC。题目是要我们求有多少个B使得
与
是等价的。我们可以对上下两式做差,得到:
由于
所以B是P-1的因数,直接用 O(P−1−−−−−√) 的时间求出P-1的因数个数即可。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
int t,p;
int main()
{
freopen("1001.in","r",stdin);
freopen("1001.out","w",stdout);
scanf("%d",&t);
while (t--)
{
scanf("%d",&p);
p--;
int k=(int)floor( sqrt( (double)p )+1e-8 ),ans=0;
for (int i=1; i<=k; i++)
if (p%i==0)
{
ans++;
if (i*i!=p) ans++;
}
printf("%d\n",ans);
}
return 0;
}
T2:数据分割(hdu6109)
题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6109
题目分析:一道并查集好题啊,明明是这么简单的算法考场上却少有人AC。先讲一下我考试时候的想法:假设所有
xi=xj
约束都在
xi≠xj
之前,我们就可以用并查集来判断是否合法,现在的问题在于如果
xi≠xj
的条件在
xi=xj
的条件之前呢?由于不等于是没有传递性的,所以这样很棘手,但可以通过合理的操作避免这个情况。我们可以先二分一个答案mid,然后对[1,mid]这一段进行CDQ分治,每一次将左边的
xi=xj
条件加进并查集,用右边的
xi≠xj
的条件去查,再将右边的
xi=xj
条件加进并查集,用左边的
xi≠xj
的条件查,如果没有冲突就递归下去查。注意并查集要用启发式合并,才能支持log(n)的时间内按顺序加边删边。考虑到可能要进行多次二分答案(要分成很多组数据),这样必定超时。
后来看了网上大神的做法,大概也是并查集+启发式合并。对于
xi≠xj
的条件,在xi所在集合的根与xj所在集合的根之间连一条双向边;对于
xi=xj
的条件,设xi集合的根连出的边数
<
xj集合的根连出的边数,则暴力查看xi集合的根是否有连向xj集合的根的边,有则不合法,一边查看一边将xi集合连出的边加进xj集合的根里,并将原先指向xi集合的根的边改指为xj所在集合的根(要实现这个的话记一个反向边就行)。加边的时候用的是启发式合并,并查集内部则用路径压缩,还要用一个栈记录操作过的xi,xj,做完一组数据后还原。这样时间复杂度为
O(nlog(n))
。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=100100;
struct edge
{
int obj;
edge *Next,*rev;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;
int fa[maxn];
int Size[maxn];
int sak[maxn<<1];
int tail=0;
int ans[maxn];
int num=0;
int n;
void Up(int x)
{
if (x==fa[x]) return;
Up(fa[x]);
fa[x]=fa[ fa[x] ];
}
void Delete()
{
while (tail)
{
int x=sak[tail];
head[x]=NULL;
fa[x]=x;
Size[x]=0;
tail--;
}
}
void Add(int x,int y)
{
cur++;
e[cur].obj=y;
e[cur].rev=&e[cur+1];
e[cur].Next=head[x];
head[x]=e+cur;
cur++;
e[cur].obj=x;
e[cur].rev=&e[cur-1];
e[cur].Next=head[y];
head[y]=e+cur;
}
int main()
{
freopen("2.in","r",stdin);
freopen("2.out","w",stdout);
scanf("%d",&n);
for (int i=1; i<=n; i++) head[i]=NULL,fa[i]=i;
for (int i=1; i<=n; i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if (x==y)
if (z) continue;
else
{
Delete();
ans[++num]=i;
continue;
}
sak[++tail]=x;
sak[++tail]=y;
Up(x);
Up(y);
if (!z)
if (fa[x]==fa[y]) ans[++num]=i,Delete();
else Add(fa[x],fa[y]),Size[ fa[x] ]++,Size[ fa[y] ]++;
else
{
if (fa[x]==fa[y]) continue;
if (Size[ fa[x] ]>Size[ fa[y] ]) swap(x,y);
edge *q=NULL;
bool sol=true;
for (edge *p=head[ fa[x] ]; p; p=p->Next)
if (p->obj==fa[y])
{
ans[++num]=i;
Delete();
sol=false;
break;
}
else p->rev->obj=fa[y],q=p;
if (!sol) continue;
if (q)
{
q->Next=head[ fa[y] ];
head[ fa[y] ]=head[ fa[x] ];
}
Size[ fa[y] ]+=Size[ fa[x] ];
fa[ fa[x] ]=fa[y];
}
}
printf("%d\n",num);
for (int i=1; i<=num; i++) printf("%d\n",ans[i]-ans[i-1]);
return 0;
}
T3:路径交(hdu6110)
题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6110
题目分析:在我做这题之前网上好像还没有人写这题的总结,所以我并不知道是否有人用比我的方法更优的解法,我的方法是
O(nlog2(n))
的,在这里说一下吧。
首先我们需要知道怎么用
O(log(n))
的时间求两条路径的交集,这里有一篇写得比较简明易懂的博客:https://blog.sengxian.com/solutions/noip-2015-day2,里面在讲NOIP2015DAY2T3的时候,讲了树上路径求交的一种简便的做法(虽然常数因子巨大-_-!)。我写的代码参考了这篇博客里介绍的方法(在此感谢博主),即a->b的路径与c->d的路径求交,交出来的路径的两个端点一定是Lca(a,b),Lca(a,c),Lca(a,d),Lca(b,c),Lca(b,d),Lca(c,d)中的其中两个。我们不妨先求出这六个点,然后枚举其中两个,看一下它们两点间的路径是否在a->b,c->d的路径上(判断是否合法),是的话看一下它们间的距离是否比之前枚举的所有合法路径长,长则用这两个点之间的路径作为a->b,c->d路径的交集。那么如何知道一条路径是否被另一条路径所包含?我们只要判断这条路径的的两个端点是否在另一条路径上即可,因为树上两点间的路径是唯一的。如何判断一个点x是否在a->b的路径上呢?设c=Lca(a,b),则若x与a,b其中一个点的Lca是x,与另一个点的Lca是c,x便在这条路径上。上面这些结论的正确性证明可以自己YY一下(其实主要是我也不会太严谨的证明啦)。
然后知道了树上路径求交,剩下的事就简单了。我们发现路径交集是满足区间信息加法的,于是可以用一个ST表存储路径交。设f[i][j]为第
i
条路径到第
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=500100;
const int maxl=22;
typedef long long LL;
struct data
{
int val;
data *dNext;
} cnt[maxn];
struct edge
{
int obj;
LL len;
edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;
int dep[maxn];
LL dis[maxn];
int fa[maxn][maxl];
int L[maxn][maxl];
int R[maxn][maxl];
int tp[6];
int P[maxn];
int max_dep=0;
int l[maxn];
int r[maxn];
int n,m,q;
int Read()
{
int x=0;
char c=getchar();
while ( c<'0' || '9'<c ) c=getchar();
while ( '0'<=c && c<='9' ) x=(x<<3)+(x<<1)+(c-'0'),c=getchar();
return x;
}
void Add(int x,int y,LL z)
{
cur++;
e[cur].obj=y;
e[cur].len=z;
e[cur].Next=head[x];
head[x]=e+cur;
}
int Max(int x,int y)
{
return ((x>y)? x:y);
}
void Dfs(int node)
{
max_dep=Max(max_dep,dep[node]);
for (edge *p=head[node]; p; p=p->Next)
{
int son=p->obj;
if (son!=fa[node][0])
{
fa[son][0]=node;
dep[son]=dep[node]+1;
dis[son]=dis[node]+p->len;
Dfs(son);
}
}
}
void Make_fa()
{
for (int j=1; j<=P[max_dep]+1; j++)
for (int i=1; i<=n; i++)
fa[i][j]=fa[ fa[i][j-1] ][j-1];
}
void Swap(int &x,int &y)
{
int z=x;
x=y;
y=z;
}
int Lca(int u,int v)
{
if (dep[u]<dep[v]) Swap(u,v);
for (data *p=&cnt[ dep[u]-dep[v] ]; p!=cnt; p=p->dNext)
u=fa[u][p->val];
if (u==v) return u;
int mj=P[ dep[u] ];
for (int j=mj; j>=0; j--)
if ( fa[u][j]!=fa[v][j] )
u=fa[u][j],v=fa[v][j];
return fa[u][0];
}
bool Check(int x,int a,int b,int c)
{
int p=Lca(a,x);
int q=Lca(b,x);
return ( ( p==c && q==x ) || ( q==c && p==x ) );
}
bool Judge(int a,int b,int c,int d,int lca)
{
return ( Check(a,c,d,lca) && Check(b,c,d,lca) );
}
void Get(int a,int b,int c,int d,int &u,int &v)
{
tp[0]=Lca(a,b);
tp[1]=Lca(a,c);
tp[2]=Lca(a,d);
tp[3]=Lca(b,c);
tp[4]=Lca(b,d);
tp[5]=Lca(c,d);
int now_len=0;
for (int i=0; i<5; i++) if (tp[i])
for (int j=i+1; j<6; j++) if (tp[j])
{
int k=Lca(tp[i],tp[j]);
LL temp=dep[ tp[i] ]+dep[ tp[j] ]-2LL*dep[k];
if (temp<=now_len) continue;
bool f=Judge(tp[i],tp[j],a,b,tp[0]);
if (!f) continue;
f&=Judge(tp[i],tp[j],c,d,tp[5]);
if (f) u=tp[i],v=tp[j],now_len=temp;
}
}
int main()
{
freopen("3.in","r",stdin);
freopen("3.out","w",stdout);
P[0]=-1;
for (int i=1; i<maxn; i++)
{
P[i]=P[i>>1]+1;
int x=(i&(-i));
cnt[i].dNext=&cnt[i-x];
while (x) cnt[i].val++,x>>=1;
cnt[i].val--;
}
n=Read();
for (int i=1; i<=n; i++) head[i]=NULL;
for (int i=1; i<n; i++)
{
int x=Read(),y=Read(),z=Read();
Add(x,y,z);
Add(y,x,z);
}
fa[1][0]=1;
Dfs(1);
Make_fa();
m=Read();
for (int i=1; i<=m; i++) l[i]=Read(),r[i]=Read();
for (int i=1; i<m; i++)
Get(l[i],r[i],l[i+1],r[i+1],L[i][0],R[i][0]);
for (int j=1; j<=P[m]; j++)
for (int i=1; i<=m; i++)
if ( i+(1<<j)<=m )
{
int mid=i+(1<<(j-1));
int a=L[i][j-1];
int b=R[i][j-1];
int c=L[mid][j-1];
int d=R[mid][j-1];
if ( a && b && c && d )
Get(a,b,c,d,L[i][j],R[i][j]);
}
q=Read();
for (int i=1; i<=q; i++)
{
int x=Read(),y=Read();
int u=0,v=0;
if (x==y) u=l[x],v=r[x];
else
{
int z=P[y-x];
y-=(1<<z);
if ( L[x][z] && R[x][z] && L[y][z] && R[y][z] )
Get(L[x][z],R[x][z],L[y][z],R[y][z],u,v);
}
if ( u && v )
{
int w=Lca(u,v);
LL ans=dis[u]+dis[v]-2LL*dis[w];
printf("%I64d\n",ans);
}
else printf("0\n");
}
return 0;
}
T4:迷宫出逃(hdu6111)
题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6111
题目分析:这题我考试的时候看完题就没去想,因为考场上没有人AC,而且当时我剩余的时间也不是很充裕。后来比赛结束之后我又重新想了想,感觉只会宽搜的做法,而且还要 O(n2) 的时间(直接暴力判重),如果答案步数很多的话可能会超时。后来上网看了看某神犇写的题解,发现真的就是用BFS做的,只不过判重用了Hash表,好像BFS出来的状态数不会超过10000个=_=!!!……简单来说就是将每个格子可不可以到达用一个unsigned long long存起来,再存一个当前坐标和一个代表是否拿到钥匙的bool类型,搜一遍即可。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=70;
const int M=100007;
const long long M1=998353244;
const long long M2=1000000007;
const long long M3=133333331;
const int u[4]={0,1,0,-1};
const int v[4]={1,0,-1,0};
typedef unsigned long long ULL;
struct data
{
ULL map;
bool key;
int X,Y;
} Hash[M];
data que[M];
int step[M];
int head,tail;
int a[maxn][maxn];
char s[maxn];
int sx,sy,kx,ky,ex,ey;
int t,n,m,ans;
void Push(int x)
{
ULL tp=que[x].map%(unsigned long long)M;
long long y=tp;
y=(y+(long long)que[x].key*M1)%M;
y=(y+(long long)que[x].X*M2)%M;
y=(y+(long long)que[x].Y*M3)%M;
while ( Hash[y].X!=-1 )
{
y++;
if (y==M) y=0;
}
Hash[y]=que[x];
}
ULL Get(int x,int y)
{
x=x*m+y;
x=n*m-x-1;
ULL tp=1;
tp<<=x;
return tp;
}
bool Check(data x)
{
ULL tp=x.map%(unsigned long long)M;
long long y=tp;
y=(y+(long long)x.key*M1)%M;
y=(y+(long long)x.X*M2)%M;
y=(y+(long long)x.Y*M3)%M;
while ( Hash[y].X!=-1 &&
( Hash[y].map!=x.map || Hash[y].key!=x.key ||
Hash[y].X!=x.X || Hash[y].Y!=x.Y ) )
{
y++;
if (y==M) y=0;
}
if (Hash[y].X==-1) return false;
return true;
}
void Bfs()
{
for (int i=0; i<M; i++) Hash[i].X=-1;
head=0,tail=1;
que[1].map=0;
for (int i=0; i<n; i++)
for (int j=0; j<m; j++)
{
int x=a[i][j];
if (x==2) x=0;
que[1].map=( (que[1].map<<1)|x );
}
que[1].key=0;
que[1].X=sx;
que[1].Y=sy;
Push(1);
while (head<tail)
{
head++;
int x=que[head].X,y=que[head].Y;
for (int i=0; i<4; i++)
{
int dx=x+u[i],dy=y+v[i];
if ( dx<0 || n<=dx || dy<0 || m<=dy ) continue;
if ( !( que[head].map & Get(dx,dy) ) )
{
if ( dx==kx && dy==ky )
{
data p;
p.map=que[head].map;
p.key=1;
p.X=dx;
p.Y=dy;
if ( !Check(p) )
que[++tail]=p,step[tail]=step[head]+1,
Push(tail);
}
if ( dx==ex && dy==ey && que[head].key )
{
ans=step[head]+1;
return;
}
if ( a[dx][dy]==2 )
{
data p;
p.map=que[head].map;
if (dx) p.map^=Get(dx-1,dy);
if (dy) p.map^=Get(dx,dy-1);
if (dx<n-1) p.map^=Get(dx+1,dy);
if (dy<m-1) p.map^=Get(dx,dy+1);
p.key=que[head].key;
p.X=dx;
p.Y=dy;
if ( !Check(p) )
que[++tail]=p,step[tail]=step[head]+1,
Push(tail);
}
if ( a[dx][dy]!=2 )
{
data p;
p.map=que[head].map;
p.key=que[head].key;
p.X=dx;
p.Y=dy;
if ( !Check(p) )
que[++tail]=p,step[tail]=step[head]+1,
Push(tail);
}
}
}
}
}
int main()
{
freopen("4.in","r",stdin);
freopen("4.out","w",stdout);
scanf("%d",&t);
for (int g=1; g<=t; g++)
{
printf("Case #%d:\n",g);
scanf("%d%d",&n,&m);
ans=-1;
for (int i=0; i<n; i++)
{
scanf("%s",&s);
for (int j=0; j<m; j++)
{
if (s[j]=='x') a[i][j]=1;
else
if (s[j]=='*') a[i][j]=2;
else
{
if (s[j]=='S') sx=i,sy=j;
if (s[j]=='K') kx=i,ky=j;
if (s[j]=='E') ex=i,ey=j;
a[i][j]=0;
}
}
}
Bfs();
printf("%d\n",ans);
}
return 0;
}
T5:今夕何夕(hdu6112)
题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6112
题目分析:这应该是本场比赛最没有技术含量的一题,也是最多WA的一题。总的来说就是看输入的日期和2月29号的关系,如果在2月29号之前,则到下一年的同一天为止经过的天数由今年是不是闰年决定;在2.29之后则要看下一年是不是闰年;如果刚好是2.29则找到下一个闰年,看看中间经过了多少天。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
int t,y,m,d;
bool Judge(int x)
{
if ((x%100)==0) return ((x%400)==0);
return ((x%4)==0);
}
int Get(int x)
{
if ( Judge(x) ) return (366%7);
return (365%7);
}
int main()
{
freopen("1005.in","r",stdin);
freopen("1005.out","w",stdout);
scanf("%d",&t);
while (t--)
{
scanf("%d-%d-%d",&y,&m,&d);
int k=0;
if ( m<2 || ( m==2 && d<29 ) )
{
k=(k+Get(y))%7;
y++;
while (k)
{
k=(k+Get(y))%7;
y++;
}
printf("%d\n",y);
}
else
if ( m==2 && d==29 )
{
if ( Judge(y+4) ) k=(k+1461)%7;
else k=(k+1460)%7;
y+=4;
while ( k || (!Judge(y)) )
{
if ( Judge(y+4) ) k=(k+1461)%7;
else k=(k+1460)%7;
y+=4;
}
printf("%d\n",y);
}
else
{
y++;
k=(k+Get(y))%7;
while (k)
{
y++;
k=(k+Get(y))%7;
}
printf("%d\n",y);
}
}
return 0;
}
T6:度度熊的01世界(hdu6113)
题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6113
题目分析:这题一开始以为很难,后来发现就是个搜索……我们先深搜一遍看看1是不是只组成了1个连通块。然后搜一遍所有0组成的连通块,如果它碰到了地图的边界,那么它就不被1所完全包含,否则它被1组成的连通块完全包含。最后看看有几个被1包含的0组成的连通块即可,还要注意一下没有1的情况。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=110;
const int u[4]={0,1,0,-1};
const int v[4]={1,0,-1,0};
bool f[maxn][maxn];
bool vis[maxn][maxn];
char s[maxn];
int num1;
int n,m;
bool Dfs(int x,int y,int z)
{
bool fl=true;
if ( x==1 || x==n || y==1 || y==m ) fl=false;
if (f[x][y]) num1--;
vis[x][y]=true;
for (int i=0; i<=3; i++)
{
int dx=x+u[i],dy=y+v[i];
if ( 1<=dx && dx<=n && 1<=dy && dy<=m && f[dx][dy]==z && !vis[dx][dy] )
fl&=Dfs(dx,dy,z);
}
return fl;
}
int main()
{
freopen("1006.in","r",stdin);
freopen("1006.out","w",stdout);
scanf("%d%d",&n,&m);
while ( n && m )
{
num1=0;
for (int i=1; i<=n; i++)
{
scanf("%s",&s);
for (int j=1; j<=m; j++)
{
f[i][j]=s[j-1]-'0';
if (f[i][j]) num1++;
}
}
memset(vis,false,sizeof(vis));
int yy=0;
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
if ( !vis[i][j] && f[i][j] )
{
Dfs(i,j,1);
yy++;
}
if (yy!=1) printf("-1\n");
else
{
int x=0;
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
if ( !vis[i][j] )
{
bool k=Dfs(i,j,0);
if (k) x++;
}
if (!x) printf("1\n");
else
if (x==1) printf("0\n");
else printf("-1\n");
}
n=0,m=0;
scanf("%d%d",&n,&m);
}
return 0;
}
总结:这次比赛考的真的很差,完全就是发挥失常。比赛开始10分钟,我浏览了一遍所有题目,先切T5。由于越快AC得分越高,我一改往常的写代码风格,用前所未有的手速敲出T5的code,然后错漏百出,有很多细节没有考虑。我写的程序不停地WA,调来调去调不过,然后我去看了看ranklist,发现好多人AC了两题了!我心里越来越慌,更加发现不了程序的错,后来甚至找了机房里和我一起考比赛的tututu的代码来对拍才发现错。由于评测姬那蜗牛般的速度,我看到自己T5显示AC的时候比赛已经过去了75min……现在想来当时实在是太紧张了,导致代码写错反而浪费更多时间。
然后用5min写了T1大水题的code(然而显示AC又过了15min)。写了一发T6,15:50交了程序,然后想T2,T3,T4,发现一题都不会。4:20的时候我又看了看我T6的code,发现没有特判地图中没有1的情况,无奈又交了一发(此时我15:50交的代码还没开始评测)。比赛一结束不知为何评测姬就跑得飞快了,然后我看到我T6WA了……我赶紧看看代码,发现本应如果没有被1包含的0的连通块输出1,有一个则输出0,而我将它们搞反了……其实本来这是连样例都不过的,但我当时心态爆炸,见到样例输出一个1,一个0,一个-1就直接交了……由于只AC了两题,而且T5WA了很多次,直接rk1200多。幸好第二天的初赛B吸取了教训稳定了心态,回到了rk120多,不然连复赛都进不了了QAQ。以后参加这种重大的ACM赛制的比赛一定要稳中求进,不要贪快,平时也要锻炼自己快速写对code的能力。还有ranklist只是用来参考下一步该做哪题,绝不能因为看见别人AC了这么多题就乱了自己的节奏。