NOIP2015 day2 [二分][DP][樹剖]

T1:
題意:在一個n長序列中取走m個數,使得任意相鄰數之間差值最小值最大。
分析:要最大值最小最小值最大,典型的二分答案套路。二分答案以後把不滿足的數給去掉(因爲去掉前一個肯定不會比去掉當前這個更優),然後注意最後一個也要算,如果要用的次數不夠了就不滿足,然後就沒了。

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=5e4+5;
int l,n,m,a[maxn];
void init()
{
    scanf("%d %d %d",&l,&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
}
int check(int lim)
{
    int last=0,tmp=m;
    for(int i=1;i<=n;i++)
        if(a[i]-last<lim){
            if(tmp)tmp--;
            else return 0;
        }
        else last=a[i];
    if((!tmp)&&l-last<lim)return 0;
    return 1;
}
void work()
{
    int lef=0,rig=l+1;
    while(lef<rig){
        int mid=(lef+rig)/2;
        if(check(mid))lef=mid+1;
        else rig=mid;
    }
    printf("%d",lef-1);
}
int main()
{
    freopen("stone.in","r",stdin);
    freopen("stone.out","w",stdout);
    init();
    work();
    return 0;
}

題意:給定兩個序列,在一個序列中取若干子序列(不重合),按順序組成第二個序列,問組成方式的種數。
分析:DP,F[k][I][J]表示枚舉到第k個字母,要組成第二個序列的後i個字母,還可以取j個子序列的方案數,那麼針對當前情況只有兩種情況,一是正在枚舉的兩個字母相同且後兩個字母相同,則不需要再取一個序列(當然還要滿足正在被枚舉的不是最後一個,已經取了一個序列的條件),當然,還有一種是正在枚舉的兩個字母相同,那麼無論後面的字母相不相同都可以再取一個序列;
需要注意四點:
1.顯然組以後一個點的空間是開不下的,這裏可以滾動數組,因爲只需要用當前和之前一次的信息所以第一維開3就可以了。(之前信息是因爲,當處於第一種情況時,加上的方法數要保證只包含取了上一個的方法,不然會多);
2.要加上上次的同一狀態的的方法總數,即不取此位但滿足的方法總數;
3.由第一點可知,這裏涉及到減法,那麼要注意方法數減少的情況,雖說一般不會影響,但如果減到負數去了取模方式就不一樣了……而且還可能輸出負數。
4.4e7可能會T,要注意常數,比如每個運算mod一次顯然比直接開Longlong要常數小,以及……開滾動數組肯定比直接賦值常數小(手動再見;

#include<iostream>
#include<cstdio>
#include<cstring>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn1=1e3+5,maxn2=2e2+5;
const int modd=1e9+7;
int cur=1,nxt=2,nxt2,n,m,k;
int f[3][maxn2][maxn2];
char a[maxn1],b[maxn2];
void init()
{
    scanf("%d %d %d",&n,&m,&k);
    f[cur][0][0]=1;
    scanf("%s",a);
    scanf("%s",b);
}
void work()
{
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            for(int z=1;z<=k;z++){
                f[nxt][j][z]=0;
                if(a[n-i]==b[m-j])
                    f[nxt][j][z]+=f[cur][j-1][z-1];
                if(a[n-i]==b[m-j]&&i!=1&&j!=1&&z&&a[n-i+1]==b[m-j+1])
                    f[nxt][j][z]+=(f[cur][j-1][z]-f[nxt2][j-1][z]+modd)%modd;
                    f[nxt][j][z]%=modd;
                    if(f[cur][j][z])
                    f[nxt][j][z]+=f[cur][j][z];
                    f[nxt][j][z]%=modd;
            }
    cur++;nxt++;nxt2++;
    cur%=3;nxt%=3;nxt2%=3; 
    f[cur][0][0]=1;
    }
    printf("%d",f[cur][m][k]);
}
int main()
{
    freopen("substring.in","r",stdin);
    freopen("substring.out","w",stdout);
    init();
    work();
    return 0;
}

T3:
題意:
對於給定的樹,要在上面走若干次,可以把任意一條路徑的代價變爲0,求變化後,所有行程同時開始,完成時間的最大值的最小值。
分析:
又是一個最大值的最小值問題,很多人會想到二分。
二分容易被卡常,當然常數把握得好的話,tarjan和樹剖是可以過的。這也是大多數AC代碼的解法。也是我覺得這道題沒水準的一個地方,啊,這種解法大多數人都能想出來吧……
出題人給的標準解法是趨近於O(N)的解法,大意是先用radix sort對邊按權值排序(邊的權值可以tarjan強制O(N)求出來),然後再從大到小枚舉邊,每次ans=min(ans,max(roa[i+1].wor,roa[1].wor-val),其中val是交集中最長邊的權值。
不過卡在兩條邊求交集上了,記得上次有人講是一條邊一直縮到在另一條邊上爲止,不過怎樣O(1)實現我還是不太清楚,問了很多人也都不會,希望有大神看到的話可以指點一下,THX!
我寫的二分加樹剖,WINDOWS下不調棧的大小會爆棧,不過評測的時候可以過畢竟評測機的棧大小和內存大小一致。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn=3e5+5;
int sta,fin,n,m,cnt,lll,mini;
int tov[2*maxn],fr[maxn],des[2*maxn],siz[maxn],w[maxn],wor[maxn*2],val[maxn];
int fa[maxn],dep[maxn],son[maxn],tii[maxn],top[maxn],son2[maxn],sig[maxn];
struct node1
{
    int lef,rig,tot;
}a[maxn*4];
struct node2
{
    int sta,fin,wor;
}b[maxn];
bool cmp(node2 x,node2 y)
{
    return x.wor>y.wor;
}
void addedge(int cur)
{
    scanf("%d %d %d",&sta,&fin,&wor[2*cur-1]);
    tov[2*cur-1]=fr[sta];fr[sta]=2*cur-1;des[2*cur-1]=fin;
    tov[2*cur]=fr[fin];fr[fin]=2*cur;des[2*cur]=sta;
    wor[2*cur]=wor[2*cur-1];
}
void dfs(int u,int fath,int ste)
{
    siz[u]=1;fa[u]=fath;dep[u]=ste;
    for(int i=fr[u];i;i=tov[i])
    if(des[i]!=fath){
        dfs(des[i],u,ste+1);
        siz[u]+=siz[des[i]];
        if(siz[des[i]]>siz[son[u]]){
            son[u]=des[i];
            son2[u]=i;
        }
    }
}
void dfs2(int u,int fath,int t,int www)
{
    top[u]=t;tii[u]=++cnt;
    w[cnt]=www;
    if(son[u])dfs2(son[u],u,t,wor[son2[u]]);
    for(int i=fr[u];i;i=tov[i])
        if(des[i]!=fath&&des[i]!=son[u])
            dfs2(des[i],u,des[i],wor[i]);
}
void pushup(int u)
{
    a[u].tot=a[2*u].tot+a[2*u+1].tot;
}
void build(int u,int lef,int rig)
{
    a[u].lef=lef;a[u].rig=rig;
    if(lef==rig){
        a[u].tot=w[lef];
        return;
    }
    int mid=(lef+rig)>>1;
    build(2*u,lef,mid);build(2*u+1,mid+1,rig);
    pushup(u);
}
int querytot(int u,int lef,int rig)
{
    if(a[u].lef==lef&&a[u].rig==rig)return a[u].tot;
    int mid=(a[u].lef+a[u].rig)>>1;
    if(rig<=mid)return querytot(2*u,lef,rig);
    else if(lef>mid)return querytot(2*u+1,lef,rig);
    return querytot(2*u,lef,mid)+querytot(2*u+1,mid+1,rig);
}
int querytotctrl(int lef,int rig)
{
    int fa1=top[lef],fa2=top[rig],ans=0;
    while(fa1!=fa2){
        if(dep[fa1]<dep[fa2]){
            swap(fa1,fa2);swap(lef,rig);
        }
        ans+=querytot(1,tii[fa1],tii[lef]);
        lef=fa[fa1];fa1=top[lef];
    }
    if(lef==rig)return ans;
    if(tii[lef]>tii[rig])swap(lef,rig);
    ans+=querytot(1,tii[son[lef]],tii[rig]);
    return ans;
}
void workedge(int cur)
{
    scanf("%d %d",&sta,&fin);
    b[cur].sta=sta;b[cur].fin=fin;
    b[cur].wor=querytotctrl(sta,fin);
}
int lca(int lef,int rig)
{
    int fa1=top[lef],fa2=top[rig];
    while(fa1!=fa2){
        if(dep[fa1]<dep[fa2]){
            swap(fa1,fa2);swap(lef,rig);
        }
        lef=fa[fa1];fa1=top[lef];
    }
    return fa1;
}
void dfs3(int u,int fath)
{
    sig[u]=val[u];
    for(int i=fr[u];i;i=tov[i])
      if(des[i]!=fath){
        dfs3(des[i],u);
        sig[u]+=sig[des[i]];
        if(sig[des[i]]==lll)
        mini=max(mini,wor[i]);
    }
}
int check(int lim)
{
    for(lll=1;lll<=m;lll++)if(b[lll].wor<=lim)break;
    lll--;clr(val);
    for(int i=1;i<=lll;i++){
        int bxqmz=lca(b[i].sta,b[i].fin);
        val[bxqmz]-=2;
        val[b[i].sta]++;val[b[i].fin]++;
    }
    mini=0;
    dfs3(1,1);
    if(b[1].wor-mini<=lim)return 1;
    return 0;
}
void init()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<n;i++)addedge(i);
    dfs(1,0,1);dfs2(1,0,1,0);build(1,1,n);
    for(int i=1;i<=m;i++)workedge(i);
    sort(b+1,b+m+1,cmp);
}
void work()
{
    int lef=0,rig=3e8+1;
    while(lef<rig){
    int mid=(lef+rig)>>1;
    if(check(mid))rig=mid;
    else lef=mid+1;
    }
    printf("%d",lef);
}
int main()
{
    freopen("transport.in","r",stdin);
    freopen("transport.out","w",stdout);
    init();
    work();
    return 0;
}

關於樹上差分,常用的有兩種:(之前寫的是lca和fa[lca]–,然後判斷的時候是隻要子節點滿足就可以,這種方法顯然可以舉出反例,但居然AC了,只能說數據太水啊太水……

1、找出被所有路徑都覆蓋的邊

在樹中將所有路徑起、始權值加1,起、始點的lca權值減2,從所有葉節點開始把權值往上累加。

最終權值爲路徑數的點到其父親的邊爲所求邊。

2、 將每條路徑(s,t)上的每個點權值增加1,求各點權值

在樹中將所有路徑起、始權值加1,起、始點的lca權值減1,lca的父親權值減1,從所有葉節點開始把權值往上累加。(上次做的松鼠的新家的差分方式)

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