大概总结下的话就是。。。。。2015那年的600分的学长,我真的是给跪了。day2的题难得真的不像话(第二题完全没有思路,第三题居然能苟到AC真的是上帝在保佑)
T1 跳石子
不那么明显的送分题,可以一眼看出是二分答案法,二分最短距离然后用队列模拟即可(唯一一道良心题)
#include<cstdio>
const int MAXN=5e4+5;
int disb[MAXN];
int L,N,M;
int ABS(int a)
{
return a>0?a:-a;
}
bool check(int mid)
{
int l=M;
int r=0;
for(int i=1;i<=N+1;i++)
{
if(ABS(disb[i]-disb[r])<mid)
{
l--;
}
else
{
r=i;
}
}
return l>=0;
}
int main()
{
//freopen("stone.in","r",stdin);
//freopen("stone.out","w",stdout);
std::scanf("%d%d%d",&L,&N,&M);
disb[0]=0;
for(int i=1;i<=N;i++)
{
std::scanf("%d",&disb[i]);
}
disb[N+1]=L;
int l,r;
l=0;
r=L+1;
int ans;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))
{
ans=mid;
l=mid+1;
}
else
{
r=mid;
}
}
printf("%d\n",ans);
return 0;
}
/*
25 5 2
2
11
14
17
21
*/
T2 子串
个人认为当天最难的题,作为一道dp题目,一如既往地有令人发指的思维难度(考试的时候根本推不出来方程,事实证明字符串这种东西是真的可以灭杀一群OIer的QWQ)
这道题总共有至少三个难点
第一个是贼操蛋的状态转移方程。。。。(在超内存的基础上)不压维的话,每一个状态会由两个状态推得
我们定义一个状态数组dp[i][j][k][0/1] 0表示子串没有连在一起的时候的方案数量,1表示连在一起的方案时候的方案数量,
而正解则是两者相加
然而如果说直接开两个三维的数组的话空间会爆炸,所以说需要用滚动数组将第一个数组的访问优化(因为s1有1000而s2长度只有200)
#include<cstdio>
#include<cstring>
const int MOD=1e9+7;
char cha;
int dp[2][205][205][2],n,m,q;
char s1[1005];
char s2[205];
int main()
{
//freopen("substring.in","r",stdin);
//freopen("substring.out","w",stdout);
scanf("%d%d%d\n",&n,&m,&q);
for(int i=1;i<=n;i++)
{
scanf("%c",&s1[i]);
}
scanf("\n");
for(int i=1;i<=m;i++)
{
scanf("%c",&s2[i]);
}
int now=1;
dp[now][0][0][0]=1;
for(int i=1;i<=n;i++)
{
now^=1;
std::memset(dp[now],0,sizeof(dp[now]));
for(int j=0;j<=m;j++)
{
for(int k=0;k<=q;k++)
{
if(s1[i]==s2[j+1]&&j!=m)
{
dp[now][j+1][k][1]=(dp[now][j+1][k][1]+dp[now^1][j][k][1])%MOD;
dp[now][j+1][k+1][1]=((dp[now][j+1][k+1][1]+dp[now^1][j][k][0])%MOD+dp[now^1][j][k][1])%MOD;
}
dp[now][j][k][0]=((dp[now][j][k][0]+dp[now^1][j][k][0])%MOD+dp[now^1][j][k][1])%MOD;
}
}
}
printf("%d\n",(dp[now][m][q][0]+dp[now][m][q][1])%MOD);
return 0;
}
第三题 二分答案+倍增
因为所有公司都是从不同地点以同一时间出发的,所以说我们完成业务的时间其实是多次询问里面长度最长的一跳链,我们要寻找的则是优化过后的最小值,所以说其实是一个查询最大值的最小值的问题,故使用二分答案二分时间来进行判断
这道题由于涉及到链查询,所以说可以用lca打标记(记录它被链经过的次数)的方法或者是树链剖分,但是事实证明链剖的时间在这道题里优于lca(更新标记的O(n)对程序的常数影响太大了),所以说lca的算法很可能只能得到95分。
还要注意先预处理LCA跟链的长度,否则会被卡常卡掉很多分
对于每一个二分出来的时间t,我们寻找超出这个t范围最多的路径,观察在所有路径都经过的链里,能否找到一跳边,使其开了虫洞以后能够刚好满足把这个t容纳下去,如果能我们就把t再往小的二分,不能就把t分得更大
写仔细一点是不会有错的
#include<cstdio>
#include<cstring>
#include<algorithm>
const int MAXN=3e5+5;
const int P=20+1;
struct Edge
{
int nxt;
int to;
int w;
}edge[MAXN<<1];
int head[MAXN];
int num;
void add(int from,int to,int w)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].w=w;
head[from]=num;
}
int anc[MAXN][P+1];
int dep[MAXN];
int wf[MAXN];
int dist[MAXN];
int idc=0;
int seq[MAXN];
int uu[MAXN];
int vv[MAXN];
int dgr[MAXN];
int cost[MAXN];
int LCA[MAXN];
void dfs(int u,int f)
{
anc[u][0]=f;
for(int p=1;p<=P;p++)
{
anc[u][p]=anc[anc[u][p-1]][p-1];
}
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==f)
{
continue;
}
dep[v]=dep[u]+1;
dist[v]=dist[u]+edge[i].w;
wf[v]=edge[i].w;
dfs(v,u);
}
seq[++idc]=u;
}
int lca(int u,int v)
{
if(dep[u]<dep[v])
{
std::swap(u,v);
}
int t=dep[u]-dep[v];
for(int p=0;t;t>>=1,p++)
{
if(t&1)
{
u=anc[u][p];
}
}
if(u==v)
{
return u;
}
for(int p=P;p>=0;p--)
{
if(anc[u][p]!=anc[v][p])
{
u=anc[u][p];
v=anc[v][p];
}
}
return anc[u][0];
}
int n,m;
void solve()
{
for(int i=1;i<=m;i++)
{
int u=uu[i];
int v=vv[i];
int ancc=lca(u,v);
cost[i]=dist[u]+dist[v]-2*dist[ancc];
LCA[i]=ancc;
}
}
bool check(int t)
{
int judge=0,rest=0;
memset(dgr,0,sizeof(dgr));
for (int i=1;i<=m;i++)
{
if(cost[i]<=t) continue;
judge++;
int u=uu[i];
int v=vv[i];
int ancc=LCA[i];
dgr[u]++;
dgr[v]++;
dgr[ancc]-=2;
rest=std::max(rest,cost[i]-t);
}
for (int i=1;i<=n;i++)
{
int u=seq[i];
dgr[anc[u][0]]+=dgr[u];
}
int hole_tim=0;
for (int i=1;i<=n;i++)
{
if (dgr[i]==judge)
{
hole_tim=std::max(hole_tim,wf[i]);
}
}
return rest<=hole_tim;
}
int main()
{
freopen("transport.in","r",stdin);
freopen("transport.out","w",stdout);
std::scanf("%d%d",&n,&m);
for(int i=1;i<=n-1;i++)
{
int u,v,w;
std::scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
for(int i=1;i<=m;i++)
{
int u,v;
std::scanf("%d%d",&u,&v);
uu[i]=u;
vv[i]=v;
}
dep[1]=1;
dfs(1,1);
solve();
int l=0;
int r=3e8;
int ans;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))
{
ans=mid;
r=mid;
}
else
{
l=mid+1;
}
}
std::printf("%d\n",ans);
return 0;
}
/*
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
*/