創新班20課3題+總結

上上個星期天,我們在創新班上了關於逆向思維的內容,並且評講了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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章