2019 Multi-University Training Contest 1补题

1002:http://acm.hdu.edu.cn/showproblem.php?pid=6579

题意:给你n个数,有m次操作,每次操作有两种

1、0 l r 表示查询[l,r]里选任意个数时异或值最大。

2、1 x 表示在n个数后面继续添加1个数x,并令n=n+1

分析:选任意个数使异或值最大肯定是线性基的题,比赛时直接上线段树维护区间线性基,时间复杂度都没算,结果T到飞起....

具体算了一下,至少是O(t*m*logn*30)也就是3e9(居然忘了线性基的合并的时候暴力合并,要*30,以为是1e8),肯定T到飞起,然鹅比赛时有没想到好的办法,只有不断优化想着卡过去,结果。。。肯定悲剧了。

具体分析注意它的修改是在末尾添加一个数,而不是直接修改某个数,所有不一定要有线段树来实现修改,可维护前缀线性基,在末尾添加数并不影响前面的维护的前缀线性基,所以可行。

至于如何取到最大值,那就要贪心一下,每次新加的数尽可能让它作为前缀线性基的基底,因为它更靠右,每次查询最值时,是查询右端点的前缀线性基,看被异或的基底是否在查询区间的左端点右侧,所以该基底越靠右越好,更有可能被选。

Ac code:

///前缀线性基
///可解决不带修改或只在最后面添加数的修改问题
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int a[maxn],f[maxn][25],pos[maxn][25];
void update(int i,int x)
{
    int k=i;
    for(int j=20; j>=0; j--) f[i][j]=f[i-1][j],pos[i][j]=pos[i-1][j];
    for(int j=20; j>=0; j--)
    {
        if(x&(1<<j))
        {
            if(!f[i][j])
            {
                f[i][j]=x;
                pos[i][j]=k;
                break;
            }
            else
            {
                if(k>pos[i][j])///使每个新出现的数尽量靠右
                {              
                    swap(k,pos[i][j]);
                    swap(x,f[i][j]);
                }
                x^=f[i][j];
            }
        }
    }
}
int main()
{
    int n,m;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        update(i,a[i]);
    }
    scanf("%d",&m);
    int l,r;
    while(m--)
    {
        scanf("%d%d",&l,&r);
        int ans=0;
        for(int j=20; j>=0; j--)
            if((ans^f[r][j])>ans&&pos[r][j]>=l)///查询时利用右端点的前缀线性基,左端点如果小于右端点的前缀线性基的第j位基底,
                ///说明可以选该基底
                ans^=f[r][j];
        printf("%d\n",ans);
    }
    return 0;
}

PS:话说居然是CF一道原题,只是这题强制在线。。

CF原题:https://codeforc.es/contest/1100/problem/F

与上面差不多,连修改都没有了。更简单

Ac code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int a[maxn],f[maxn][25],pos[maxn][25];
void update(int i,int x)
{
    int k=i;
    for(int j=20; j>=0; j--) f[i][j]=f[i-1][j],pos[i][j]=pos[i-1][j];
    for(int j=20; j>=0; j--)
    {
        if(x&(1<<j))
        {
            if(!f[i][j])
            {
                f[i][j]=x;
                pos[i][j]=k;
                break;
            }
            else
            {
                if(k>pos[i][j])
                {
                    swap(k,pos[i][j]);
                    swap(x,f[i][j]);
                }
                x^=f[i][j];
            }
        }
    }
}
int main()
{
    int n,m;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        update(i,a[i]);
    }
    scanf("%d",&m);
    int l,r;
    while(m--)
    {
        scanf("%d%d",&l,&r);
        int ans=0;
        for(int j=20; j>=0; j--)
            if((ans^f[r][j])>ans&&pos[r][j]>=l)
                ans^=f[r][j];
        printf("%d\n",ans);
    }
    return 0;
}

 

1005:http://acm.hdu.edu.cn/showproblem.php?pid=6582

题意:给你n个结点,m条边(有可能有重边),要你删除一些边使最短路变大。

分析:就是一个最小割(最大流)的板子题,选出所有最短路径,构成一个新的图,跑一遍最大流就好了。

选出所有最短路上的边的方法:spfa跑出了到1的最短dist后,遍历1->m,如果dist[u]+(u,v)==dist[v]就表明(u,v)这条边一定在最短路径上。

Ac code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+1;
const ll INF=1e18;
int head[maxn],tot;
struct Edge
{
    int v,nxt;
    ll w;
} edg[maxn];
ll dist[maxn];
bool vis[maxn];
struct edge
{
    int from, to;
    ll cap, flow;//分别是起点,终点,容量,流量
    edge(int u, int v, ll c, ll f):from(u), to(v), cap(c), flow(f) {}
};
int n, m;//n为点数,m为边数
vector<edge>e;//保存所有边的信息
vector<int>G[maxn];//邻接表,G[i][j]保存节点i的第j条边在e数组里面的编号
ll a[maxn];//每个点目前流经的水量
int p[maxn];//p[i]从原点s到终点t的节点i的前一条边的编号
int u[maxn],v[maxn],w[maxn];
void init(int n)
{
    tot=0;
    memset(head,-1,sizeof head);
    for(int i = 0; i <= n; i++)G[i].clear();
    e.clear();
}
void addedge(int u, int v, int c)
{
    e.push_back(edge(u, v, c, 0));//正向边
    e.push_back(edge(v, u, 0, 0));//反向边,容量为0
    int m = e.size();
    G[u].push_back(m - 2);
    G[v].push_back(m - 1);
}
ll Maxflow(int s, int t)//起点为s,终点为t
{
    int flow = 0;
    for(;;)
    {
        memset(a, 0, sizeof(a));//从原点s开始放水,最初每个点的水量都为0
        queue<int>Q;//BFS拓展队列
        Q.push(s);
        a[s] = INF;//原点的水设置成INF
        while(!Q.empty())
        {
            int x = Q.front();//取出目前水流到的节点
            Q.pop();
            for(int i = 0; i < G[x].size(); i++)//所有邻接节点
            {
                edge& now = e[G[x][i]];
                if(!a[now.to] && now.cap > now.flow)
                    //a[i]为0表示i点还未流到
                    //now.cap > now.flow 说明这条路还没流满
                    //同时满足这两个条件,水流可以流过这条路
                {
                    p[now.to] = G[x][i];//反向记录路径
                    a[now.to] = min(a[x], now.cap - now.flow);
                    //流到下一点的水量为上一点的水量或者路径上还可以流的最大流量,这两者取最小值
                    Q.push(now.to);//将下一个节点入队列
                }
            }
            if(a[t])break;//如果已经流到了终点t,退出本次找增广路
        }
        if(!a[t])break;//如果所有路都已经试过,水不能流到终点,说明已经没有增广路,已经是最大流
        for(int u = t; u != s; u = e[p[u]].from)//反向记录路径
        {
            e[p[u]].flow += a[t];//路径上所有正向边的流量增加流到终点的流量
            e[p[u]^1].flow -= a[t];//路径上所有反向边的流量减少流到终点的流量
        }
        flow += a[t];//最大流加上本次流到终点的流量
    }
    return flow;
}
void addedge1(int u,int v,ll w)
{
    edg[tot].v=v;
    edg[tot].w=w;
    edg[tot].nxt=head[u];
    head[u]=tot++;
}
void spfa(int s)
{
    queue<int>q;
    for(int i=1; i<=n; i++) dist[i]=INF;
    memset(vis,0,sizeof vis);
    dist[s]=0;
    vis[s]=1;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u]; ~i; i=edg[i].nxt)
        {
            int v=edg[i].v;
            if(dist[v]>dist[u]+edg[i].w)
            {
                dist[v]=dist[u]+edg[i].w;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);

                }
            }
        }
    }
}
void addedge2(int m)
{
    for(int i=1; i<=m; i++)
    {
        if(dist[u[i]]+w[i]==dist[v[i]]) ///求所有最短路径上的边
            addedge(u[i],v[i],w[i]);
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init(n);
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
            addedge1(u[i],v[i],w[i]);
        }
        spfa(1);
        addedge2(m);
        printf("%lld\n",Maxflow(1,n));
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值