[2018多省省隊聯測] Day 2 部分解題報告

T1劈配 題目太長傳送門

第一問考慮動態加邊網絡流。按照志願順序加邊,如果能夠bfs成功就能成功增廣。記錄答案即可。
難點在第二問,如何確定最少增長几名呢。二分是比較好想到的。。有一個妙妙的做法就是,在第一問時存下每一個狀態的圖,然而第二問二分到相應的圖,上去增廣。小技巧就是不需要的邊及時刪掉。
這題還是比較妙的,動態加邊加上存殘圖。

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;

const int MAXN=500;
const int INF=1e9+7;

int T,C,n,m,s,t,p[MAXN];

struct edge{
    int to,w,rev;
    edge(int a,int b,int c){
        to=a,w=b,rev=c;
    }
};

struct Gra{
    vector<edge>vec[MAXN];
    inline void add(int u,int v,int w){
        vec[u].push_back(edge(v,w,vec[v].size()));
        vec[v].push_back(edge(u,0,vec[u].size()-1));
    }
    inline void del(int u,int v){
        vec[u].pop_back();
        vec[v].pop_back();
    }
    queue<int>q;
    int dep[MAXN];
    bool bfs(){
        memset(dep,0,sizeof(dep));
        dep[s]=1;q.push(s);
        while(q.size()){
            int u=q.front();q.pop();
            for(int i=0;i<vec[u].size();i++){
                edge &E=vec[u][i];
                int v=E.to;
                if(E.w&&!dep[v]){
                    dep[v]=dep[u]+1;
                    q.push(v);  
                }
            }
        }
        if(!dep[t])return 0;
        return 1;
    }
    int dfs(int u,int flow){
        if(u==t||flow==0)return flow;
        for(int i=0;i<vec[u].size();i++){
            edge &E=vec[u][i];
            int v=E.to;
            if(dep[v]==dep[u]+1&&E.w){
                int tmp=dfs(v,min(flow,E.w));
                if(tmp){
                    E.w-=tmp;
                    vec[v][E.rev].w+=tmp;
                    return tmp;
                }
            }
        }
        return 0;
    }
}G[MAXN];

vector<int>Pip[MAXN][MAXN];

int ans[MAXN];

void solve1(){
    for(int i=1;i<=n;i++){
        G[i]=G[i-1];
        G[i].add(s,i,1);
        for(int j=1;j<=m;j++){
            for(int k=0;k<Pip[i][j].size();k++)G[i].add(i,n+Pip[i][j][k],1);
            if(G[i].bfs()){
                G[i].dfs(s,INF);
                ans[i]=j;
                break;
            }
            for(int k=Pip[i][j].size()-1;k>=0;k--)G[i].del(i,n+Pip[i][j][k]);
        }
    }
    for(int i=1;i<=n;i++){
        if(!ans[i])ans[i]=m+1;
        printf("%d ",ans[i]);
    }
    puts("");
}

bool check(int x,int pos){
    Gra TG=G[pos-1];
    TG.add(s,x,1);
    for(int i=1;i<=m;i++){
        if(i>p[x])return 0;
        for(int j=0;j<Pip[x][i].size();j++)TG.add(x,n+Pip[x][i][j],1);
        if(TG.bfs()){
            if(i<=p[x])return 1;
            return 0;
        }
    }
    return 0;
}

int find(int x){
    int l=1,r=x-1,ans=x;
    while(l<=r){
        int mid=l+r>>1;
        if(check(x,x-mid))r=mid-1,ans=mid;
        else l=mid+1;
    }
    return ans;
}

void solve2(){
    for(int i=1;i<=n;i++){
        if(p[i]>=ans[i])printf("0 ");
        else printf("%d ",find(i));
    }
    puts("");
}

void work(){
    for(int i=1;i<=m;i++){
        int tmp;
        scanf("%d",&tmp);   
        G[0].add(n+i,t,tmp);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int tmp;
            scanf("%d",&tmp);
            if(!tmp)continue;
            Pip[i][tmp].push_back(j);
        }
    }
    for(int i=1;i<=n;i++)scanf("%d",&p[i]);
    solve1();
    solve2();
}

void mem(){
    for(int i=0;i<=405;i++){
        for(int j=0;j<=405;j++)G[i].vec[j].clear();
    }
    memset(p,0,sizeof(p));
    memset(ans,0,sizeof(ans));
    for(int i=0;i<=405;i++)
        for(int j=0;j<=405;j++)Pip[i][j].clear();
}

int main(){
    //freopen("mentor.in","r",stdin);
    //freopen("mentor.out","w",stdout);
    scanf("%d%d",&T,&C);
    while(T--){
        mem();
        scanf("%d%d",&n,&m);
        s=0,t=n+m+1;
        work();
    }
    return 0;   
}

2.林克卡特樹
這裏寫圖片描述
對於題目名字。。2333。很有趣。
10%的數據,直接求帶負權的樹的直徑。記錄每個節點往下的最長鏈和次長鏈然後瞎算一下。
20%的數據,只刪一條邊連一條邊。我們考慮刪完一條邊以後答案是什麼樣的,可以從一個子樹中選出一條鏈,從另一個子樹中也選一個鏈,比較大小。也可以把他們首尾相接。所以直接枚舉刪邊,分別求直徑相加即可。

對於60%的數據,通過20%的數據我們想到了刪一條邊時,加的邊是選2個鏈接起來。更多的刪邊加邊不也是這樣嘛。
但有一種情況需要考慮,就是一條鏈上多個分叉,我們這樣是無論如何都接不起來的(成環),所以題目轉化爲了選k+1條點不相交的鏈。f[i][j][k] 表示以i爲根的子樹,已經選了j個鏈,其中鏈上的點有 k(0<=k<=2) 條邊與點i相交。

轉移的時候分 子樹順序轉移。其中 g 是當前的答案,f 是已經轉移過的答案,這樣就不會有衝突了,詳細見代碼..

代碼60pts

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int MAXN=3e5+10;
const ll INF=1e18+7;

struct edge{
    int to,next;ll w;
}e[MAXN<<1];

int head[MAXN],cnt=0,n,k;

inline void add(int u,int v,ll w){
    e[++cnt]=(edge){v,head[u],w},head[u]=cnt;
    e[++cnt]=(edge){u,head[v],w},head[v]=cnt;
}

int size[MAXN];
ll f[MAXN][105][3],g[105][3];

//f[i][0][0]=0;f[i][1][1]=0;
//f[j][0]=f[k][0..2] k<=j

void dfs(int u,int fa){
    f[u][0][0]=f[u][1][1]=0;
    size[u]=1;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa)continue;
        dfs(v,u);
        for(int j=0;j<=k;j++)
            for(int t=0;t<=2;t++)g[j][t]=-INF;
        int limit=min(k,size[u]);
        for(int j=0;j<=limit;j++){
            for(int t=0;t<=size[v];t++){//此時的f還未被v子樹更新。 
                if(t+j>k+1)break;//兩條鏈連成一個時會-1 
                for(int tt=0;tt<=2;tt++)
                    for(int tt2=0;tt2<=2;tt2++)g[t+j][tt]=max(g[t+j][tt],f[u][j][tt]+f[v][t][tt2]);
                if(t||j)g[t+j-1][2]=max(g[t+j-1][2],f[u][j][1]+f[v][t][1]+e[i].w);
            //  if(t||j)g[t+j][2]=max(g[t+j][2],f[u][j][1]+f[v][t][0]+e[i].w);
                g[t+j][1]=max(g[t+j][1],f[u][j][0]+f[v][t][1]+e[i].w);//選k個鏈合併時改變了u節點的度數 
            //  g[t+j][1]=max(g[t+j][1],f[u][j][0]+f[v][t][0]+e[i].w);
            }
        }
        for(int j=0;j<=k;j++)
            for(int t=0;t<=2;t++)f[u][j][t]=g[j][t];
        size[u]+=size[v];
    }
}

int main(){
//  freopen("lct.in","r",stdin);
//  freopen("lct.out","w",stdout);
    scanf("%d%d",&n,&k);
    k++;
    for(int i=1;i<n;i++){
        int u,v;ll w;
        scanf("%d%d%lld",&u,&v,&w);
        add(u,v,w);
    }
    for(int i=0;i<=n;i++){
        for(int j=0;j<=k;j++)
            f[i][j][0]=f[i][j][1]=f[i][j][2]=-INF;
    }
    dfs(1,1);
    cout<<max(f[1][k][0],max(f[1][k][1],f[1][k][2]))<<endl; 
}

T3不會寫 完

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