写在前面
因为本解题报告采用了许多大佬的题解与代码,所以记录不过来便设置为转载,如果侵犯了大佬的权益请联系我(也就是转侵删)
跳石头
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终 点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达 终点 为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
思路
二分答案即可
代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=50005;
int a[maxn],n,m,ans;
bool check(int x)
{
int sum,last;
sum=0;last=0;
for (int i=1;i<=n;i++)
{
if (a[i]-last<x) { sum+=1;continue;}//如果小于就移走当前的石头
last=a[i];
}
if (sum>m) return 0;
return 1;
}
int main()
{
int L,R,mid;
scanf("%d%d%d\n",&L,&n,&m);
for (int i=1;i<=n;i++) scanf("%d\n",&a[i]);
n+=1;
a[n]=L;R=L;
ans=L=0;
while (L<=R)
{
mid=L+(R-L)/2;
if (check(mid))
{
ans=mid;
L=mid+1;
}
else R=mid-1;
}//二分过程
printf("%d",ans);
return 0;
}
子串
1. 思路
首先讲一种最简单的DP吧(也是我最容易看懂的),不过在洛谷上似乎有更优解
我们令f[i][j][k][0/1]表示A串用了前i个字符,B串已覆盖前j个字符,目前为止已经选了k个子串,最后的0/1表示A串的这个字符选了没有(0没选,1选了)。
为了得出状态转移方程,我们分情况讨论:先看f[i][j][k][1](当前位选了),显然当且仅当a[i]=b[j]的时候它才有意义,否则f[i][j][k][1]=0。
到这个状态有三种方法:
上一位没有选,新开一个子串
上一位选了,延续这个子串
上一位选了,但是仍然新开一个子串
因此,我们有
f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1]。
状态转移方程中的三项分别对应上述三种情况。注意,因为我们规定了A的这一位必须选(因为状态的最后一维是1),所以所有前驱状态一定是f[i-1][j-1][…][…]。
然后讨论另一种情况:这个字符不选。
这个比较简单,到这个状态有两种方法:
上一位没有选,现在仍然不选
上一位选了,结束这个子串
因此,我们有
f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]。
合起来就是
f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1](a[i]=b[j])
f[i][j][k][1]=0(a[i]!=b[j])
f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]
状态转移方程有了,边界也容易确定:f[0][0][0][0]=1。至于最终答案,显然是f[n][m][k][0]+f[n][m][k][1]
然后用滚动数组压掉一维就可以
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1005,M=205,K=205,MOD=1000000007;
int n,m,p;
char a[N],b[M];
int f[2][M][K][2],pre=1,now=0,ans=0;
void dp(){
//因为我们发现i时的值只由i-1传递过来,所以只需要用pre,now来定义i-1,i;
f[pre][0][0][0]=f[now][0][0][0]=1;//初始化
for(int i=1;i<=n;i++,swap(now,pre))//把现在的now数组存i,pre为i-1;
for(int j=1;j<=m;j++)
for(int k=1;k<=p;k++){
if(a[i]==b[j])
f[now][j][k][1]=(f[pre][j-1][k-1][0]+f[pre][j-1][k][1])%MOD;
else
f[now][j][k][1]=0;
f[now][j][k][0]=(f[pre][j][k][0]+f[now][j][k][1])%MOD;//DP主体;
}
}
int main() {
scanf("%d%d%d%s%s",&n,&m,&p,a+1,b+1);
dp();
cout<<f[pre][m][p][0];
return 0;
}
运输计划
- 题意
给你一棵树,有很多条路径,让你把一条边的边权设为0,使路径的最大值最小 - 思路
lca+二分
大概思路就是首先二分一个答案(时间),然后对各个计划进行遍历求值,记录时间大于答案的路线并维护最大差值sum,然后寻找这几条路线里有没有公共边且使最长公共边长度变为0后是否合法,不合法就把时间扩大,合法就缩小
(思路确实简单)
/*算法:tarjan + 二分
思路:先找lca,可以在dfs的时候就用tarjan直接算出,然后找出s -> t的路程,即 q[i].dis = deep[q[i].from] + deep[q[i].to] - 2* deep[q[i].lca];
继续进行二分,对于路程大于假定答案的就cnt++,不然就break,
因为提前排过序,最后发现只能对于cnt==num[i]的点进行虫洞操作,最后如果大于就继续缩小,反之增大上限。
c++代码如下:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<stack>
using namespace std;
const int N = 3e5 + 1;
int head[N],f[N],deep[N],v[N],num[N],headm[N],tot,n,m;
inline void read(int&x)
{
x=0;char c;int sign=1;
do{ c=getchar();if(c=='-') sign=-1;}while(c<'0'||c>'9');
do{ x=x*10+c-'0';c=getchar();}while(c<= '9'&&c>='0');
x *= sign;
}//读入优化
struct node
{
int to,next,val;
}edge[N*4];//结构体
struct str
{
int from,to,lca,dis;
}q[N];
void add(int head[],int a,int b,int val)
{
edge[tot].next=head[a];
edge[tot].to=b;
edge[tot].val=val;
head[a]=tot++;
}
inline int get_fa(int x) { return x==f[x]?x:f[x]=get_fa(f[x]);}//找爸爸(你的就不用找了,因为就是我[滑稽])
void dfs(int u,int fa)
{
f[u]=u;
for(int i=headm[u];~i/*这表示i=-1就结束因为初始化时数组定的是-1(第87行)*/;i=edge[i].next)
{
node &e=edge[i];//其实觉得这一段没这么必要,不过简洁了些;
if(f[q[e.to].from]&&u==q[e.to].to) q[e.to].lca=get_fa(q[e.to].from);
if(f[q[e.to].to]&&u==q[e.to].from) q[e.to].lca=get_fa(q[e.to].to);//求LCA
}
for(int i=head[u];~i;i=edge[i].next)
{
node&e=edge[i];
if(e.to==fa) continue;
deep[e.to]=deep[u]+e.val;//更新深度
dfs(e.to,u);
v[e.to]=e.val;
}
f[u]=fa;//确立父子关系
}
const bool cmp(str a,str b){ return a.dis>b.dis;}
void find(int u ,int fa)
{
for(int i=head[u];~i;i=edge[i].next)
{
node&e=edge[i];
if(e.to==fa)continue;
find(e.to,u);
num[u]+=num[e.to];
}
}//标记上推;
bool check(int x)
{
int cnt=0,dec=0;
memset(num,0,sizeof(num));//清零标记;
for(int i=1;i<=m;i++)
if(q[i].dis>x){cnt++;dec=max(dec,q[i].dis-x);num[q[i].from]++;num[q[i].to]++;num[q[i].lca]-=2;}
//处理非法路,因为待会标记会上推所以最近公共祖先先-=2,这样等下推的时候最近公共祖先的数组值仍会为0,就不会影响结果了;
else break;
find(1,1);
for(int i=1;i<=n;i++)
if(cnt==num[i]&&v[i]>=dec)return 1;//如果删去的这个路的值比最大差值大就return 1否则return 0;
return 0;
}
int main()
{
memset(head,-1,sizeof(head));memset(headm,0,sizeof(headm));//初始化数组;
read(n);read(m);
int u,v,val;
for(int i=1;i<n;i++)
{
read(u);read(v);read(val);
add(head,u,v,val);add(head,v,u,val);//边数组
}
for(int i=1;i<=m;i++)
read(q[i].from),read(q[i].to),add(headm,q[i].from,i,0),add(headm,q[i].to,i,0);//询问数组;
dfs(1,1);//也就是所谓的trajan;
int l=0,r=0,num=0;
for(int i=1;i<=m;i++){q[i].dis=deep[q[i].from]+deep[q[i].to]-2*deep[q[i].lca];r=max(q[i].dis,r);}//求最短路;
sort(q+1,q+1+m,cmp);//将路径从大到小排序便于进行
while(l<=r)
{
int mid=(l+r)>> 1;
if(check(mid))num=mid,r=mid-1;
else l=mid+1;
}//二分主体;
cout<<num<<endl;
return 0;
}
这里算是个人的一些感想???
1.这几道题目可是折磨了我好久(主要第三道题,也体会到了大佬们的超神)
2.找题解的过程里发现好多大佬的网页是真的可爱(雾?),我也最喜欢去看这样的题解,因为看起来不是冷冰冰的,运用到与人交流上或许也是如此。
3.发现大佬的思路跟我完全是不同的,比喻为走路的话我或许是想着怎么走到,而大佬却考虑
的是怎么快点走到,所以有好多优化啊那种东西完全看不懂,这或许也就级别的差距吧,我也要好好的加强自己了,至少要追到能看到大佬的背影(%%%)