从鸽王那儿看到的一组好题, (Itst) 推荐的,作为复健第一套题试试水。自己做了 (A) 、 (B) 、 (E) 三个,道阻且长。
(A-Two) (Rabbits)
解法
结论显然, ((a+b)|(y-x)) 时在整数秒相遇,相遇所用时间为 (frac{y-x}{a+b}) 秒;否则不能相遇。
(Code)
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
int ret=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<1)+(ret<<3)+ch-'0';
ch=getchar();
}
return ret*f;
}
int t,x,y,a,b;
int main()
{
t=read();
for(register int i=1;i<=t;i++)
{
x=read();
y=read();
a=read();
b=read();
y=y-x;
b=a+b;
if(y%b==0)
printf("%d
",y/b);
else
printf("-1
");
}
return 0;
}
(B-) (Longest) (Palindrome)
解法
注意到 (n leqslant 100) 且 (m leqslant 50) ,故考虑 (O(n^2m)) 逐字扫描任意两个串是否互为回文串,标记下来分左右两边输出,另外判断是否有本身就是回文串的字符串放在正中间即可。
(Code)
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int ret=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<1)+(ret<<3)+ch-'0';
ch=getchar();
}
return ret*f;
}
int n,m,cnt=0,slfnum=0,l[105],r[105];
char str[105][55];
bool flag,slf;
int main()
{
n=read();
m=read();
for(register int i=1;i<=n;i++)
scanf("%s",str[i]);
for(register int i=1;i<=n;i++)
{
slf=1;
for(register int j=0;j<m;j++)
if(str[i][j]!=str[i][m-1-j])
slf=0;
if(slf)
slfnum=i;
for(register int j=i+1;j<=n;j++)
{
flag=1;
for(register int k=0;k<m;k++)
if(str[i][k]!=str[j][m-1-k])
flag=0;
if(flag)
{
cnt++;
l[cnt]=i;
r[cnt]=j;
}
}
}
if(slfnum==0)
printf("%d
",2*m*cnt);
else
printf("%d
",2*m*cnt+m);
for(register int i=1;i<=cnt;i++)
printf("%s",str[l[i]]);
if(slfnum>0)
printf("%s",str[slfnum]);
for(register int i=cnt;i>=1;i--)
printf("%s",str[r[i]]);
printf("
");
return 0;
}
(C-) (Air) (Conditioner) (好题)
解法
首先提出引理:任一时刻可能达到的温度是一段连续(整数)区间。由于每一分钟到下一分钟的温度变化只能为 (+1) , (-1) 或 (0) ,故引理正确。由引理,将问题转化为任一时刻求出的可能区间是否满足顾客的需求,问题的实质是一个区间比较问题,时间复杂度 (O(q sum n)) 。具体求出可能的温度区间的方式是标记上下界,并随时间变化扩大上下界,随顾客需求变化缩小上下界。
提示
本题能做到线性的关键是不考虑具体的路径,只考虑路径的集合(以可能的温度区间呈现)。换言之,具体的合法路径是什么对于本题来说并不重要。
思考:上面做法的正确性?
个人认为在于最终所选路径只有合法和不合法两种,该做法筛去了所有不合法的路径(疑问:怎么证明?),只留下了保证是合法路径的路径集合。欢迎在评论区证明该题做法的正确性。
(Code)
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int ret=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<1)+(ret<<3)+ch-'0';
ch=getchar();
}
return ret*f;
}
int q,n,m,lm,rm,t[105],l[105],r[105];
bool flg;
int main()
{
q=read();
for(register int i=1;i<=q;i++)
{
n=read();
m=read();
t[0]=0;
flg=1;
for(register int j=1;j<=n;j++)
{
t[j]=read();
l[j]=read();
r[j]=read();
}
lm=m;
rm=m;
for(register int j=1;j<=n;j++)
{
lm=lm-(t[j]-t[j-1]);
rm=rm+(t[j]-t[j-1]);
if(r[j]<lm||rm<l[j])
flg=0;
lm=max(lm,l[j]);
rm=min(rm,r[j]);
}
if(lm>rm)
flg=0;
if(flg)
printf("YES
");
else
printf("NO
");
}
return 0;
}
(D-) (Shortest) (and) (Longest) (LIS) (好题)
解法
考虑怎样构造这个序列。(该题的考点是构造)下面分别讲解错误的和正确的思路。
错解:首先考虑怎样满足大小限制,再通过安排数的(相对)大小实现 (LIS) 最长/最短。
从错解走向正解:怎样安排数的(相对)大小呢?显然是使满足大小关系的小数放左边、大数放右边时 (LIS) 最长,最短的情况同理。
正解:反向考虑,首先不安排大小顺序,先考虑无大小限制时怎样使 (LIS) 最长/最短。显然,递增序列 (lbrace 1,2,3,...,n
brace) 的 (LIS) 最长,递减序列 (lbrace n,n-1,n-2,...,1
brace) 的 (LIS) 最短。接下来考虑大小关系限制,对于构造好的序列,将连续 (">") 的 (LIS) 最长序列的子序列反转、将连续 ("<") 的 (LIS) 最短序列的子序列反转即可满足大小限制。
补充
(LIS) 长度的下界是连续 ("<") 的长度。达到该长度的构造如上。 (LIS) 长度的上界是连续 (">") 区间的个数加上连续 (">") 区间以外数的个数(另一种答案是 ("<") 的个数 (+1) )。达到该长度的构造如上。
(Code)
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int ret=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<1)+(ret<<3)+ch-'0';
ch=getchar();
}
return ret*f;
}
int t,n,l,lismin[200005],lismax[200005];
char str[200005];
bool flag,slf;
int main()
{
t=read();
for(register int i=1;i<=t;i++)
{
n=read();
scanf("%s",str);
for(register int j=1;j<=n;j++)
{
lismax[j]=j;
lismin[j]=n+1-j;
}
l=0;
for(register int j=0;j<n-1;j++)
{
if(str[j]=='<')
{
if(l==0)
l=j+1;
}
else
{
if(l>0)
{
reverse(lismin+l,lismin+j+2);
l=0;
}
}
}
if(l>0)
{
reverse(lismin+l,lismin+n+1);
l=0;
}
for(register int j=0;j<n-1;j++)
{
if(str[j]=='>')
{
if(l==0)
l=j+1;
}
else
{
if(l>0)
{
reverse(lismax+l,lismax+j+2);
l=0;
}
}
}
if(l>0)
{
reverse(lismax+l,lismax+n+1);
l=0;
}
for(register int j=1;j<n;j++)
printf("%d ",lismin[j]);
printf("%d
",lismin[n]);
for(register int j=1;j<n;j++)
printf("%d ",lismax[j]);
printf("%d
",lismax[n]);
}
return 0;
}
(E) (1-Trees) (and) (Queries)
解法
思路简单,细节比较多。考虑加边 ((x,y)) 之后形成一个环,任意两点 (a,b) 之间的最短路径有三种可能: (a-x-y-b) , (a-y-x-b) , (a-b) 。由于同一条边、同一个点可以重复经过,这三种路径长度任意一个不比 (k) 大且与 (k) 的奇偶性相同即可。(这是一个简化的做法,因为可以无限前进偶数步,所以实际上只需考虑奇偶性)
细节
注意树的建图是双向的。注意倍增 (LCA) 的一些细节。
(Code)
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int ret=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<1)+(ret<<3)+ch-'0';
ch=getchar();
}
return ret*f;
}
int n,u,v,queries,x,y,a,b,k,dis1,dis2,dis3,rlen,r,edgenum,t;
int ver[200005],w[200005],nxt[200005],head[100005],d[100005],dist[100005],f[100005][18];
queue<int> q;
inline void addedge(int x,int y,int z)
{
ver[++edgenum]=y;
w[edgenum]=z;
nxt[edgenum]=head[x];
head[x]=edgenum;
}
inline void bfs()
{
q.push(1);
d[1]=1;
while(q.size())
{
int x=q.front();
q.pop();
for(register int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(d[y])
continue;
d[y]=d[x]+1;
dist[y]=dist[x]+w[i];
f[y][0]=x;
for(register int j=1;j<=t;j++)
f[y][j]=f[f[y][j-1]][j-1];
q.push(y);
}
}
}
inline int lca(int x,int y)
{
if(d[x]>d[y])
swap(x,y);
for(register int i=t;i>=0;i--)
if(d[f[y][i]]>=d[x])
y=f[y][i];
if(x==y)
return x;
for(register int i=t;i>=0;i--)
{
if(f[y][i]!=f[x][i])
{
y=f[y][i];
x=f[x][i];
}
}
return f[x][0];
}
int main()
{
n=read();
for(register int i=1;i<n;i++)
{
u=read();
v=read();
addedge(u,v,1);
addedge(v,u,1);
}
t=(int)(log(n)/log(2))+1;
bfs();
queries=read();
for(register int i=1;i<=queries;i++)
{
x=read();
y=read();
a=read();
b=read();
k=read();
dis1=dist[a]+dist[b]-2*dist[lca(a,b)];
dis2=dist[a]+dist[x]-2*dist[lca(a,x)]+dist[y]+dist[b]-2*dist[lca(y,b)]+1;
dis3=dist[b]+dist[x]-2*dist[lca(b,x)]+dist[y]+dist[a]-2*dist[lca(y,a)]+1;
rlen=dist[x]+dist[y]-2*dist[lca(x,y)]+1;
if((k>=dis1)&&((k-dis1)%2==0))
{
printf("YES
");
continue;
}
if((k>=dis2)&&((k-dis2)%2==0))
{
printf("YES
");
continue;
}
if((k>=dis3)&&((k-dis3)%2==0))
{
printf("YES
");
continue;
}
printf("NO
");
}
return 0;
}
未完待续