[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] f [ i ] [ j ] [ k ] 表示以i为根的子树,已经选了j个链,其中链上的点有 k(0<=k<=2) k ( 0 <= k <= 2 ) 条边与点i相交。

转移的时候分 子树顺序转移。其中 g 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不会写 完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值