创新班20课3题+总结

21 篇文章 0 订阅
2 篇文章 0 订阅

上上个星期天,我们在创新班上了关于逆向思维的内容,并且评讲了salesman,stone,message三题,现在我做一下总结。

逆向思维,就是从问题的最后面(结果)开始出发,一步一步往前推,一直推到开始的状态,从而求出问题的解。在这个求解的过程中,我们通常是记住一个或几个关键点(保留关键字),再逆向推出答案。(等会详细讲。。。)

接下来是评讲三题:salesman,stone,message.

salesman

salesman是这几题之中最难的,也是noip的第四题。这题我想了好久也没想明白,最后使用了江老师左边与右边分别求最大值的方法才做出来。具体是这样的:

创新班20课3题+总结 - 周正华 - 周正华的博客

 

然后就一直按照123走,直到做完。

但是如果每次都找一遍最大值,那就是超时的60O(n?)算法了,但是我们还要进一步优化,那就是用胜者树来维护左边那一部分的最大值(这个我也搞了好久)。每次找到左边后就把左边的修改成-1。找到右边,就把找到的位置与当前位置之间的都插入进胜者树中。这样左边就能找到最大值。至于右边,那就只能for循环搜一遍了,至于数据如果太坑,那就没办法了。

程序(因为我写的时候有点乱,因此程序感觉也乱乱的,可能会有些缺陷):

#include <iostream>
#include <fstream>
using namespace std;
ifstream fin("salesman.in");
ofstream fout("salesman.out");
#define cin fin
#define cout fout

int n,d;
int s[100001];
int a[100001];
int ans=0;
struct Tnode
{
int num,pos; //num是当前节点存的数,pos是当前节点的数在原数组里的位置 ,则pos+d为胜者树最底层的第pos个数的位置

};
Tnode tree[4*100001];

int test=0; //用于调试请忽略

void _insert(int x,int pos) //这里是把x插入到最底层第pos个中
{
pos+=d;
tree[pos].num=x;
tree[pos].pos=pos-d;
int now=pos/2;
while(now>=1) //不停往上找(管老师教我们用递归插入、修改,我还是觉得这个好用点)
{
if(tree[now*2].num>tree[now*2+1].num)
tree[now]=tree[now*2];
else tree[now]=tree[now*2+1];
now/=2;
}
}

void build() //建树,d指最底层有多少个节点
{
d=1;
while(d<n) d*=2;
for(int i=1;i<=n;i++)
_insert(a[i],i);
}

int askmax(int l,int r,int i,int j,int pos)

//这里指查找i~j区间内的最大值,返回的是找到的数在原数组里的位置
{
int left,right;
if (l==i && r==j) return tree[pos].pos;
if (j<=(l+r)/2) return askmax(l,(l+r)/2,i,j,pos*2); //不断二分找
else if (i > (l+r) / 2) return askmax((l+r)/2+1,r,i,j,pos*2+1);
left = tree[askmax(l,(l+r)/2,i,(l+r)/2,pos*2)].pos;
right = tree[askmax((l+r)/2+1,r,(l+r)/2+1,j,pos*2+1)].pos;
if(tree[left].num>tree[right].num) return left;
else return right;
}

int _search(int x) //这里求原数组中第x个数及其后面的数中的最大值
{ //返回的是Xi+2(di-dj)最大值在原数组中的位置
int Maxpos=x;
for(int i=x;i<=n;i++)
if(((s[Maxpos]-s[x])*2+a[Maxpos])<((s[i]-s[x])*2+a[i]))
Maxpos=i;
return Maxpos;
}

int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++) cin>>a[i];
build();
int temp=_search(1);
ans+=a[temp]+s[temp]*2;
cout<<ans<<endl;
int x,y,np=temp; //np指new_pos
_insert(-1,np);
for(int i=2;i<=n;i++)
{
if(np==n) //如果找到头了,那就直接在里面找Xi最大值
{
ans+=tree[1].num;
_insert(-1,tree[1].pos);
cout<<ans<<endl;
continue;
}
x=askmax(1,n,1,np-1,1); //x指当前位置左边的Xi最大值
y=_search(np+1); //y指当前位置右边的Xi+2(Di-Dnp)最大值
if(tree[x+d].num>=tree[y+d].num+(s[y]-s[np])*2)

ans+=tree[1].num,_insert(-1,tree[1].pos);

//左边更优就ans加上x,并把找到的点修改为-1

else //ans加上y,插入np~y之间的点,修改-1
{
ans+=tree[y+d].num+(s[y]-s[np])*2;
for(int j=np+1;j<=y;j++)
_insert(a[j],j);
np=tree[y+d].pos;
_insert(-1,np);
}
cout<<ans<<endl;
}

return 0;
}

#include <iostream>
#include <fstream>
using namespace std;
ifstream fin("stone.in");
ofstream fout("stone.out");
#define cin fin
#define cout fout

int L,n,m;
int d[50003];

int main()
{
cin>>L>>n>>m;
d[0]=0;d[n+1]=L;
for(int i=1;i<=n;i++)
cin>>d[i];
int l=0,r=L,mid=0;
int step=0;
int lastl=l,lastr=r;
int now=0;
while(l<r)
{
mid=(l+r+1)/2; //+1 ?
for(int i=0;i<=n;i++)
{
now=i;
while(d[i+1]-d[now]<mid && i<=n) step++,i++;
}
// cout<<step<<" "<<mid<<" "<<l<<" "<<r<<endl;
if(step<=m) l=mid;
else r=mid-1;
step=0;
if(l==lastl && r==lastr) break;
lastl=l;lastr=r;
}
cout<<lastl<<endl;

return 0;
}

#include <iostream>
#include <fstream>
using namespace std;
ifstream fin("message.in");
ofstream fout("message.out");
#define cin fin
#define cout fout
int n;
int fa[2000001];
int d[2000001];

int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>fa[i];
int now=1;
int sum,ans=100000000;
int j=0;
int start=0;
for(int i=1;i<=n;i++)
{
if(d[i]!=0) continue;
j=i;start=now;
do
{
d[j]=now;
now++;
j=fa[j];

}
while(d[j]==0);
if(d[j]>=start)
ans=min(ans,now-d[j]);
// cout<<now<<" "<<d[j]<<" "<<j<<endl;
}
cout<<ans<<endl;
/*
for(int i=1;i<=n;i++)
cout<<d[i]<<" ";
cout<<endl;
*/

return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值