TEST2018.4.21(Noip2015提高组Day1)
为2019级lizw0520zzh的学生献上福利!!!
T1 跳石头(第一道二分):
看到最小的最大值就知道是二分了。。。
对这个最大值进行二分,每次判断是否只需要移走小于M块石头就行了。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=50000+233;
int a[MAXN];
int L,N,M;
bool check(int d) {
int ll=M,position=0;
for(int i=1;i<=N+1;i++) {
if(a[i]-a[position]<d) ll--;
else position=i;
}
return ll>=0?true:false;
}
int main() {
freopen("stone.in","r",stdin);
freopen("stone.out","w",stdout);
scanf("%d%d%d",&L,&N,&M);
for(int i=1;i<=N;i++)
scanf("%d",a+i);
a[0]=0;a[N+1]=L;
int left=0,right=L+10;
while(right-left>1) {
int mid=left+right>>1;
if(check(mid)) left=mid;
else right=mid;
}
printf("%d\n",left);
}
发现Noip2015特别喜欢二分
T2 字串:
一道很恶心的dp
一开始是这样想的:dp[i][j][k]代表A串位置到了i,B串到了j,已经用了k个子串。
那么就有:能匹配时,方案数为:单独使用当前字符为一个子串+与前面相连形成一个子串。
结果程序写出来后自己手出了一组样例发现错了???!于是就发现了如果不使用当前字符,情况是什么样的呢?这种情况就被完美忽略了不是吗?
于是就有了下面这种神奇的dp:
设s[i][j][k]为A用到了i,B用到了j,已经用了k个子串, 并且一定用了当前字符(A[i])时的方案数。
设f[i][j][k]为A用到了i,B用到了j,已经用了k个子串, 无论用不用当前字符(A[i])时的方案数总和。
然后“显而易见”的,可以得到如下转移方程:
s[i][j][k]=f[i-1][j-1][k-1]+s[i-1][j-1][k] 和 f[i][j][k]=f[i-1][j][k]+s[i][j][k]
但是一看这个空间限制,一脸黑人问号了。。才128MB,玩什么?
于是真正考试的时候我这道题是错了的qwq
等到听评讲的时候才想到第一维可以写成滚动数组
然后就有如下代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 1000000007
using namespace std;
int a,b,k;
char A[1002],B[1002];
int f[3][1000][1000],s[3][1000][1000];
int main() {
freopen("substring.in","r",stdin);
freopen("substring.out","w",stdout);
scanf("%d%d%d\n",&a,&b,&k);
scanf("%s",A+1); scanf("%s",B+1);
int now=1,past=0;
f[0][0][0]=1;
for(int e=1;e<=a;e++) {
f[now][0][0]=1;
for(int j=1;j<=b;j++){
for(int t=1;t<=k;t++){
if(A[e]==B[j])s[now][j][t]=(s[past][j-1][t]+f[past][j-1][t-1])%mod;
else s[now][j][t]=0;
f[now][j][t]=(f[past][j][t]+s[now][j][t])%mod;
}
}
swap(now,past);
}
cout<<f[past][b][k];
return 0;
}
但是!!!我并没有这样写!!!
我的代码有点难理解,而且不如上面那个代码简单,但是只用开一个数组,长这样:(大家可以看注释)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int Mod=1000000000+7;
const int N=1000+23;
const int M=200+23;
char cha[N],chb[M];
int f[2][N][M];
int n,m,k,u,o,p,ans;
int main() {
freopen("substring.in","r",stdin);
freopen("substring.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
scanf("%s%s",cha,chb);
for(int i=n;i>=1;i--) cha[i]=cha[i-1]; //预处理字符串
for(int i=m;i>=1;i--) chb[i]=chb[i-1]; //预处理字符串
for(int i=1;i<=n;i++)
if(cha[i]==chb[1]) f[0][i][1]=1; //预处理相同开头的字符串
for(int l=2;l<=m;l++) { //dp过程
u=(o+1)%2;
for(int j=1;j<=k;j++) {
p=0;
for(int i=1;i<=n;i++) {
p=(p+f[o][i-1][j-1])%Mod;
if(cha[i]==chb[l]) f[u][i][j]=(f[o][i-1][j]+p)%Mod; //更新方案数
else f[u][i][j]=0; //如果这两个位置上的字符不同,就没有方案
}
}
o=u;
}
for(int i=1;i<=n;i++)
ans=(ans+f[o][i][k])%Mod; //累加方案数
printf("%d\n",ans);
return 0;
}
是不是很难懂?反正我想了好久。。。
T3 运输计划(第二道二分):
是道难题,反正考试的时候打个暴力拿了15分(非洲脸,明明理论60的,可能有小细节出错了吧)。
2015真的特别喜欢二分啊,而我不太擅长的就是二分了(还好我生的晚),这道题的正解是:
lca+二分+树上倍增(为了提速)+差分
详细的大家可以看这位大佬的博客,只是他的代码太丑了(他应该不会看到这篇博客的。。),所以我就贴自己的啦~~
大佬博客: ouql的博客
Tarjan求lca是个好东西(手动滑稽)
代码:
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
const int N=300000+233;
struct Node {
int too,pre,dis;
}e[N<<1],lik[N<<1];
int n,m,x,y,z,l,r,mid,mx;
int val[N],fa[N];
int tot,tot2;
int q[N][4],cntt[N],cnt[N];
int last1[N],last2[N],d[N];
bool v[N];
void add(int x,int y,int z) {
e[++tot].too=y;
e[tot].dis=z;
e[tot].pre=last1[x];
last1[x]=tot;
}
void add2(int x,int y,int z) {
lik[++tot2].too=y;
lik[tot2].dis=z;
lik[tot2].pre=last2[x];
last2[x]=tot2;
}
int find(int x) {
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void tarjan(int x) {
v[x]=1;fa[x]=x;
for(int i=last2[x];i;i=lik[i].pre)
if(v[lik[i].too])
q[lik[i].dis][3]=find(lik[i].too);
for(int i=last1[x],too=e[i].too;i;i=e[i].pre,too=e[i].too)
if(!v[too]) {
d[too]=d[x]+e[i].dis;
tarjan(too);
fa[too]=x;
}
}
int dfs(int x,int fa) {
int sum=cnt[x];
for(int i=last1[x],too=e[i].too;i;i=e[i].pre,too=e[i].too)
if(too!=fa) {
val[too]=e[i].dis;
sum+=dfs(too,x);
}
return cntt[x]=sum;
}
bool ck() {
int total=0,mxx=0;
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=m;i++)
if(d[q[i][1]]+d[q[i][2]]-2*d[q[i][3]]>mid) {
total++;
cnt[q[i][1]]++;
cnt[q[i][2]]++;
cnt[q[i][3]]-=2;
}
dfs(1,0);
for(int i=1;i<=n;i++)
if(cntt[i]==total&&val[i]>mxx)
mxx=val[i];
return mx-mxx<=mid;
}
int main() {
freopen("transport.in","r",stdin);
freopen("transport.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n-1;i++) {
scanf("%d%d%d",&x,&y,&z);
add(x,y,z),add(y,x,z);
}
for(int i=1;i<=m;i++) {
scanf("%d%d",&x,&y);
add2(x,y,i);add2(y,x,i);
q[i][1]=x;q[i][2]=y;
}
tarjan(1);
for(int i=1;i<=m;i++)
mx=max(mx,d[q[i][1]]+d[q[i][2]]-2*d[q[i][3]]);
l=0;r=mx;
while(l<r) {
mid=(l+r)/2;
if(ck()) r=mid;
else l=mid+1;
}
printf("%d\n",l);
}
最后照例祝大家早日AK(反正我是这辈子都不可能AK了)!